Skip to content

Commit

Permalink
Merge branch 'master' into v2.x
Browse files Browse the repository at this point in the history
  • Loading branch information
BobDotCom committed Feb 10, 2022
2 parents 1d21f2d + f38896a commit 22bc506
Show file tree
Hide file tree
Showing 33 changed files with 685 additions and 397 deletions.
3 changes: 1 addition & 2 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# Warning: We have a feature freeze till release
That means we won't accept new features for now. Only bug fixes.
<!-- Warning: No new features will be merged until the next stable release. -->

## Summary

Expand Down
4 changes: 2 additions & 2 deletions discord/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
__author__ = 'Pycord Development'
__license__ = 'MIT'
__copyright__ = 'Copyright 2015-2021 Rapptz & Copyright 2021-present Pycord Development'
__version__ = '2.0.0b3'
__version__ = '2.0.0b4'

__path__ = __import__('pkgutil').extend_path(__path__, __name__)

Expand Down Expand Up @@ -74,6 +74,6 @@ class VersionInfo(NamedTuple):
serial: int


version_info: VersionInfo = VersionInfo(major=2, minor=0, micro=0, releaselevel='beta', serial=3)
version_info: VersionInfo = VersionInfo(major=2, minor=0, micro=0, releaselevel='beta', serial=4)

logging.getLogger(__name__).addHandler(logging.NullHandler())
59 changes: 33 additions & 26 deletions discord/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ async def get_desynced_commands(self, guild_id: Optional[int] = None) -> List[Di
# We can suggest the user to upsert, edit, delete, or bulk upsert the commands

return_value = []
cmds = copy.deepcopy(self.pending_application_commands)
cmds = self.pending_application_commands.copy()

if guild_id is None:
registered_commands = await self.http.get_global_commands(self.user.id)
Expand Down Expand Up @@ -282,7 +282,7 @@ async def get_desynced_commands(self, guild_id: Optional[int] = None) -> List[Di
if type(to_check[check]) == list:
for opt in to_check[check]:

cmd_vals = [val.get(opt, MISSING) for val in as_dict[check]]
cmd_vals = [val.get(opt, MISSING) for val in as_dict[check]] if check in as_dict else []
for i, val in enumerate(cmd_vals):
# We need to do some falsy conversion here
# The API considers False (autocomplete) and [] (choices) to be falsy values
Expand Down Expand Up @@ -379,11 +379,12 @@ async def register_commands(
if commands is None:
commands = self.pending_application_commands

commands = copy.deepcopy(commands)
commands = [copy.copy(cmd) for cmd in commands]

for cmd in commands:
to_rep_with = [guild_id] if guild_id is not None else guild_id
cmd.guild_ids = to_rep_with
if guild_id is not None:
for cmd in commands:
to_rep_with = [guild_id]
cmd.guild_ids = to_rep_with

is_global = guild_id is None

Expand Down Expand Up @@ -537,7 +538,8 @@ async def sync_commands(
for cmd in commands:
cmd.guild_ids = guild_ids

registered_commands = await self.register_commands(commands, force=force)
global_commands = [cmd for cmd in commands if cmd.guild_ids is None]
registered_commands = await self.register_commands(global_commands, force=force)

cmd_guild_ids = []
registered_guild_commands = {}
Expand All @@ -549,10 +551,12 @@ async def sync_commands(
if unregister_guilds is not None:
cmd_guild_ids.extend(unregister_guilds)
for guild_id in set(cmd_guild_ids):
guild_commands = [cmd for cmd in commands if cmd.guild_ids is not None and guild_id in cmd.guild_ids]
registered_guild_commands[guild_id] = await self.register_commands(
commands,
guild_commands,
guild_id=guild_id,
force=force)
force=force
)

# TODO: 2.1: Remove this and favor permissions v2
# Global Command Permissions
Expand All @@ -572,13 +576,15 @@ async def sync_commands(
# Permissions (Roles will be converted to IDs just before Upsert for Global Commands)
global_permissions.append({"id": i["id"], "permissions": cmd.permissions})

for guild_id, guild_data in registered_guild_commands.items():
commands = registered_guild_commands[guild_id]
for guild_id, commands in registered_guild_commands.items():
guild_permissions: List = []

for i in commands:
cmd = find(lambda cmd: cmd.name == i["name"] and cmd.type == i["type"] and int(i["guild_id"]) in
cmd.guild_ids, self.pending_application_commands)
cmd = find(lambda cmd: cmd.name == i["name"] and cmd.type == i["type"] and cmd.guild_ids is not None
and int(i["guild_id"]) in cmd.guild_ids, self.pending_application_commands)
if not cmd:
# command has not been added yet
continue
cmd.id = i["id"]
self._application_commands[cmd.id] = cmd

Expand All @@ -588,7 +594,8 @@ async def sync_commands(
for perm in cmd.permissions
if perm.guild_id is None
or (
perm.guild_id == guild_id and perm.guild_id in cmd.guild_ids
perm.guild_id == guild_id and cmd.guild_ids is not None and perm.guild_id in
cmd.guild_ids
)
]
guild_permissions.append(
Expand All @@ -601,7 +608,8 @@ async def sync_commands(
for perm in global_command["permissions"]
if perm.guild_id is None
or (
perm.guild_id == guild_id and perm.guild_id in cmd.guild_ids
perm.guild_id == guild_id and cmd.guild_ids is not None and perm.guild_id in
cmd.guild_ids
)
]
guild_permissions.append(
Expand Down Expand Up @@ -636,7 +644,7 @@ async def sync_commands(
}
)
else:
print(
raise RuntimeError(
"No Role ID found in Guild ({guild_id}) for Role ({role})".format(
guild_id=guild_id, role=permission["id"]
)
Expand Down Expand Up @@ -669,9 +677,8 @@ async def sync_commands(

# Make sure we don't have over 10 overwrites
if len(new_cmd_perm["permissions"]) > 10:
print(
"Command '{name}' has more than 10 permission overrides in guild ({guild_id}).\nwill only use "
"the first 10 permission overrides.".format(
raise RuntimeError(
"Command '{name}' has more than 10 permission overrides in guild ({guild_id}).".format(
name=self._application_commands[new_cmd_perm["id"]].name,
guild_id=guild_id,
)
Expand All @@ -687,7 +694,7 @@ async def sync_commands(
self.user.id, guild_id, guild_cmd_perms
)
except Forbidden:
print(
raise RuntimeError(
f"Failed to add command permissions to guild {guild_id}",
file=sys.stderr,
)
Expand Down Expand Up @@ -722,8 +729,8 @@ async def process_application_commands(self, interaction: Interaction, auto_sync
if auto_sync is None:
auto_sync = self.auto_sync_commands
if interaction.type not in (
InteractionType.application_command,
InteractionType.auto_complete
InteractionType.application_command,
InteractionType.auto_complete,
):
return

Expand Down Expand Up @@ -1399,15 +1406,15 @@ class Bot(BotBase, Client):
.. versionadded:: 1.3
debug_guilds: Optional[List[:class:`int`]]
Guild IDs of guilds to use for testing commands. This is similar to debug_guild.
The bot will not create any global commands if a debug_guilds is passed.
Guild IDs of guilds to use for testing commands.
The bot will not create any global commands if debug guild IDs are passed.
..versionadded:: 2.0
.. versionadded:: 2.0
auto_sync_commands: :class:`bool`
Whether or not to automatically sync slash commands. This will call sync_commands in on_connect, and in
:attr:`.process_application_commands` if the command is not found. Defaults to ``True``.
..versionadded:: 2.0
.. versionadded:: 2.0
"""

pass
Expand Down
6 changes: 3 additions & 3 deletions discord/cog.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

import discord.utils
from . import errors
from .commands import _BaseCommand, ApplicationCommand, ApplicationContext
from .commands import _BaseCommand, ApplicationCommand, ApplicationContext, SlashCommandGroup

__all__ = (
'CogMeta',
Expand Down Expand Up @@ -275,8 +275,8 @@ def walk_commands(self) -> Generator[ApplicationCommand, None, None]:
A command or group from the cog.
"""
for command in self.__cog_commands__:
if command.parent is None:
yield command
if isinstance(command, SlashCommandGroup):
yield from command.walk_commands()

def get_listeners(self) -> List[Tuple[str, Callable[..., Any]]]:
"""Returns a :class:`list` of (name, function) listener pairs that are defined in this cog.
Expand Down
41 changes: 39 additions & 2 deletions discord/commands/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"""
from __future__ import annotations

from typing import TYPE_CHECKING, Optional, TypeVar, Union
from typing import TYPE_CHECKING, Optional, TypeVar, Union, Dict, List

import discord.abc
from discord.interactions import InteractionMessage
Expand Down Expand Up @@ -99,15 +99,19 @@ async def _get_channel(self) -> Optional[InteractionChannel]:

async def invoke(self, command: ApplicationCommand[CogT, P, T], /, *args: P.args, **kwargs: P.kwargs) -> T:
r"""|coro|
Calls a command with the arguments given.
This is useful if you want to just call the callback that a
:class:`.ApplicationCommand` holds internally.
.. note::
This does not handle converters, checks, cooldowns, pre-invoke,
or after-invoke hooks in any matter. It calls the internal callback
directly as-if it was a regular function.
You must take care in passing the proper arguments when
using this function.
Parameters
-----------
command: :class:`.ApplicationCommand`
Expand Down Expand Up @@ -159,7 +163,7 @@ def message(self) -> Optional[Message]:
def user(self) -> Optional[Union[Member, User]]:
return self.interaction.user

author = user
author: Optional[Union[Member, User]] = user

@property
def voice_client(self) -> Optional[VoiceProtocol]:
Expand All @@ -172,6 +176,39 @@ def voice_client(self) -> Optional[VoiceProtocol]:
def response(self) -> InteractionResponse:
return self.interaction.response

@property
def selected_options(self) -> Optional[List[Dict]]:
"""The options and values that were selected by the user when sending the command.
Returns
-------
Optional[List[Dict]]
A dictionary containing the options and values that were selected by the user when the command was processed, if applicable.
Returns ``None`` if the command has not yet been invoked, or if there are no options defined for that command.
"""
return self.interaction.data.get("options", None)

@property
def unselected_options(self) -> Optional[List[Option]]:
"""The options that were not provided by the user when sending the command.
Returns
-------
Optional[List[:class:`.Option`]]
A list of Option objects (if any) that were not selected by the user when the command was processed.
Returns ``None`` if there are no options defined for that command.
"""
if self.command.options is not None: # type: ignore
if self.selected_options:
return [
option
for option in self.command.options # type: ignore
if option.to_dict()["name"] not in [opt["name"] for opt in self.selected_options]
]
else:
return self.command.options # type: ignore
return None

@property
def respond(self) -> Callable[..., Awaitable[Union[Interaction, WebhookMessage]]]:
"""Callable[..., Union[:class:`~.Interaction`, :class:`~.Webhook`]]: Sends either a response
Expand Down
10 changes: 9 additions & 1 deletion discord/commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
from ..enums import SlashCommandOptionType, ChannelType
from ..errors import ValidationError, ClientException
from ..member import Member
from ..message import Message
from ..message import Attachment, Message
from ..user import User
from ..utils import find, get_or_fetch, async_all, utcnow

Expand Down Expand Up @@ -495,6 +495,9 @@ def qualified_name(self) -> str:
else:
return self.name

def __str__(self) -> str:
return self.qualified_name

def _set_cog(self, cog):
self.cog = cog

Expand Down Expand Up @@ -752,6 +755,11 @@ async def _invoke(self, ctx: ApplicationContext) -> None:
elif op.input_type == SlashCommandOptionType.string and (converter := op.converter) is not None:
arg = await converter.convert(converter, ctx, arg)

elif op.input_type == SlashCommandOptionType.attachment:
_data = ctx.interaction.data["resolved"]["attachments"][arg]
_data["id"] = int(arg)
arg = Attachment(state=ctx.interaction._state, data=_data)

kwargs[op._parameter_name] = arg

for o in self.options:
Expand Down
39 changes: 25 additions & 14 deletions discord/commands/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

from ..enums import ChannelType, SlashCommandOptionType


__all__ = (
"ThreadOption",
"Option",
Expand Down Expand Up @@ -70,20 +71,30 @@ def __init__(self, input_type: Any, /, description: str = None, **kwargs) -> Non
self.converter = input_type
input_type = SlashCommandOptionType.string
else:
_type = SlashCommandOptionType.from_datatype(input_type)
if _type == SlashCommandOptionType.channel:
if not isinstance(input_type, tuple):
input_type = (input_type,)
for i in input_type:
if i.__name__ == "GuildChannel":
continue
if isinstance(i, ThreadOption):
self.channel_types.append(i._type)
continue

channel_type = channel_type_map[i.__name__]
self.channel_types.append(channel_type)
input_type = _type
try:
_type = SlashCommandOptionType.from_datatype(input_type)
except TypeError as exc:
from ..ext.commands.converter import CONVERTER_MAPPING

if input_type in CONVERTER_MAPPING:
self.converter = CONVERTER_MAPPING[input_type]
input_type = SlashCommandOptionType.string
else:
raise exc
else:
if _type == SlashCommandOptionType.channel:
if not isinstance(input_type, tuple):
input_type = (input_type,)
for i in input_type:
if i.__name__ == "GuildChannel":
continue
if isinstance(i, ThreadOption):
self.channel_types.append(i._type)
continue

channel_type = channel_type_map[i.__name__]
self.channel_types.append(channel_type)
input_type = _type
self.input_type = input_type
self.required: bool = (
kwargs.pop("required", True) if "default" not in kwargs else False
Expand Down
Loading

0 comments on commit 22bc506

Please sign in to comment.