Skip to content

Commit

Permalink
Merge branch 'Rapptz:master' into guide/slash-commands
Browse files Browse the repository at this point in the history
  • Loading branch information
ika2kki committed Oct 1, 2023
2 parents 2c7bc56 + e1aa6cc commit 283cf24
Show file tree
Hide file tree
Showing 35 changed files with 1,327 additions and 363 deletions.
2 changes: 1 addition & 1 deletion discord/__main__.py
Expand Up @@ -157,7 +157,7 @@ async def bot_check_once(self, ctx):
async def cog_command_error(self, ctx, error):
# error handling to every command in here
pass
async def cog_app_command_error(self, interaction, error):
# error handling to every application command in here
pass
Expand Down
4 changes: 2 additions & 2 deletions discord/abc.py
Expand Up @@ -1842,7 +1842,7 @@ def _get_voice_state_pair(self) -> Tuple[int, int]:
async def connect(
self,
*,
timeout: float = 60.0,
timeout: float = 30.0,
reconnect: bool = True,
cls: Callable[[Client, Connectable], T] = VoiceClient,
self_deaf: bool = False,
Expand All @@ -1858,7 +1858,7 @@ async def connect(
Parameters
-----------
timeout: :class:`float`
The timeout in seconds to wait for the voice endpoint.
The timeout in seconds to wait the connection to complete.
reconnect: :class:`bool`
Whether the bot should automatically attempt
a reconnect if a part of the handshake fails
Expand Down
4 changes: 3 additions & 1 deletion discord/activity.py
Expand Up @@ -732,7 +732,9 @@ class CustomActivity(BaseActivity):

__slots__ = ('name', 'emoji', 'state')

def __init__(self, name: Optional[str], *, emoji: Optional[PartialEmoji] = None, **extra: Any) -> None:
def __init__(
self, name: Optional[str], *, emoji: Optional[Union[PartialEmoji, Dict[str, Any], str]] = None, **extra: Any
) -> None:
super().__init__(**extra)
self.name: Optional[str] = name
self.state: Optional[str] = extra.pop('state', name)
Expand Down
3 changes: 3 additions & 0 deletions discord/app_commands/commands.py
Expand Up @@ -1548,6 +1548,9 @@ def __init__(
if not self.description:
raise TypeError('groups must have a description')

if not self.name:
raise TypeError('groups must have a name')

self.parent: Optional[Group] = parent
self.module: Optional[str]
if cls.__discord_app_commands_has_module__:
Expand Down
35 changes: 16 additions & 19 deletions discord/app_commands/errors.py
Expand Up @@ -28,6 +28,7 @@

from ..enums import AppCommandOptionType, AppCommandType, Locale
from ..errors import DiscordException, HTTPException, _flatten_error_dict
from ..utils import _human_join

__all__ = (
'AppCommandError',
Expand Down Expand Up @@ -242,13 +243,7 @@ class MissingAnyRole(CheckFailure):
def __init__(self, missing_roles: SnowflakeList) -> None:
self.missing_roles: SnowflakeList = missing_roles

missing = [f"'{role}'" for role in missing_roles]

if len(missing) > 2:
fmt = '{}, or {}'.format(', '.join(missing[:-1]), missing[-1])
else:
fmt = ' or '.join(missing)

fmt = _human_join([f"'{role}'" for role in missing_roles])
message = f'You are missing at least one of the required roles: {fmt}'
super().__init__(message)

Expand All @@ -271,11 +266,7 @@ def __init__(self, missing_permissions: List[str], *args: Any) -> None:
self.missing_permissions: List[str] = missing_permissions

missing = [perm.replace('_', ' ').replace('guild', 'server').title() for perm in missing_permissions]

if len(missing) > 2:
fmt = '{}, and {}'.format(", ".join(missing[:-1]), missing[-1])
else:
fmt = ' and '.join(missing)
fmt = _human_join(missing, final='and')
message = f'You are missing {fmt} permission(s) to run this command.'
super().__init__(message, *args)

Expand All @@ -298,11 +289,7 @@ def __init__(self, missing_permissions: List[str], *args: Any) -> None:
self.missing_permissions: List[str] = missing_permissions

missing = [perm.replace('_', ' ').replace('guild', 'server').title() for perm in missing_permissions]

if len(missing) > 2:
fmt = '{}, and {}'.format(", ".join(missing[:-1]), missing[-1])
else:
fmt = ' and '.join(missing)
fmt = _human_join(missing, final='and')
message = f'Bot requires {fmt} permission(s) to run this command.'
super().__init__(message, *args)

Expand Down Expand Up @@ -530,8 +517,18 @@ def __init__(self, child: HTTPException, commands: List[CommandTypes]) -> None:
messages = [f'Failed to upload commands to Discord (HTTP status {self.status}, error code {self.code})']

if self._errors:
for index, inner in self._errors.items():
_get_command_error(index, inner, commands, messages)
# Handle case where the errors dict has no actual chain such as APPLICATION_COMMAND_TOO_LARGE
if len(self._errors) == 1 and '_errors' in self._errors:
errors = self._errors['_errors']
if len(errors) == 1:
extra = errors[0].get('message')
if extra:
messages[0] += f': {extra}'
else:
messages.extend(f'Error {e.get("code", "")}: {e.get("message", "")}' for e in errors)
else:
for index, inner in self._errors.items():
_get_command_error(index, inner, commands, messages)

# Equivalent to super().__init__(...) but skips other constructors
self.args = ('\n'.join(messages),)
4 changes: 2 additions & 2 deletions discord/app_commands/transformers.py
Expand Up @@ -525,7 +525,7 @@ class Transform:
.. versionadded:: 2.0
"""

def __class_getitem__(cls, items) -> _TransformMetadata:
def __class_getitem__(cls, items) -> Transformer:
if not isinstance(items, tuple):
raise TypeError(f'expected tuple for arguments, received {items.__class__.__name__} instead')

Expand Down Expand Up @@ -570,7 +570,7 @@ async def range(interaction: discord.Interaction, value: app_commands.Range[int,
await interaction.response.send_message(f'Your value is {value}', ephemeral=True)
"""

def __class_getitem__(cls, obj) -> _TransformMetadata:
def __class_getitem__(cls, obj) -> RangeTransformer:
if not isinstance(obj, tuple):
raise TypeError(f'expected tuple for arguments, received {obj.__class__.__name__} instead')

Expand Down
2 changes: 1 addition & 1 deletion discord/app_commands/tree.py
Expand Up @@ -1240,7 +1240,7 @@ async def _call(self, interaction: Interaction[ClientT]) -> None:
await command._invoke_autocomplete(interaction, focused, namespace)
except Exception:
# Suppress exception since it can't be handled anyway.
pass
_log.exception('Ignoring exception in autocomplete for %r', command.qualified_name)

return

Expand Down
8 changes: 8 additions & 0 deletions discord/channel.py
Expand Up @@ -1600,6 +1600,7 @@ async def create_instance(
topic: str,
privacy_level: PrivacyLevel = MISSING,
send_start_notification: bool = False,
scheduled_event: Snowflake = MISSING,
reason: Optional[str] = None,
) -> StageInstance:
"""|coro|
Expand All @@ -1621,6 +1622,10 @@ async def create_instance(
You must have :attr:`~Permissions.mention_everyone` to do this.
.. versionadded:: 2.3
scheduled_event: :class:`~discord.abc.Snowflake`
The guild scheduled event associated with the stage instance.
.. versionadded:: 2.4
reason: :class:`str`
The reason the stage instance was created. Shows up on the audit log.
Expand All @@ -1647,6 +1652,9 @@ async def create_instance(

payload['privacy_level'] = privacy_level.value

if scheduled_event is not MISSING:
payload['guild_scheduled_event_id'] = scheduled_event.id

payload['send_start_notification'] = send_start_notification

data = await self._state.http.create_stage_instance(**payload, reason=reason)
Expand Down
84 changes: 83 additions & 1 deletion discord/components.py
Expand Up @@ -25,7 +25,7 @@
from __future__ import annotations

from typing import ClassVar, List, Literal, Optional, TYPE_CHECKING, Tuple, Union, overload
from .enums import try_enum, ComponentType, ButtonStyle, TextStyle, ChannelType
from .enums import try_enum, ComponentType, ButtonStyle, TextStyle, ChannelType, SelectDefaultValueType
from .utils import get_slots, MISSING
from .partial_emoji import PartialEmoji, _EmojiTag

Expand All @@ -40,8 +40,10 @@
ActionRow as ActionRowPayload,
TextInput as TextInputPayload,
ActionRowChildComponent as ActionRowChildComponentPayload,
SelectDefaultValues as SelectDefaultValuesPayload,
)
from .emoji import Emoji
from .abc import Snowflake

ActionRowChildComponentType = Union['Button', 'SelectMenu', 'TextInput']

Expand All @@ -53,6 +55,7 @@
'SelectMenu',
'SelectOption',
'TextInput',
'SelectDefaultValue',
)


Expand Down Expand Up @@ -263,6 +266,7 @@ class SelectMenu(Component):
'options',
'disabled',
'channel_types',
'default_values',
)

__repr_info__: ClassVar[Tuple[str, ...]] = __slots__
Expand All @@ -276,6 +280,9 @@ def __init__(self, data: SelectMenuPayload, /) -> None:
self.options: List[SelectOption] = [SelectOption.from_dict(option) for option in data.get('options', [])]
self.disabled: bool = data.get('disabled', False)
self.channel_types: List[ChannelType] = [try_enum(ChannelType, t) for t in data.get('channel_types', [])]
self.default_values: List[SelectDefaultValue] = [
SelectDefaultValue.from_dict(d) for d in data.get('default_values', [])
]

def to_dict(self) -> SelectMenuPayload:
payload: SelectMenuPayload = {
Expand All @@ -291,6 +298,8 @@ def to_dict(self) -> SelectMenuPayload:
payload['options'] = [op.to_dict() for op in self.options]
if self.channel_types:
payload['channel_types'] = [t.value for t in self.channel_types]
if self.default_values:
payload["default_values"] = [v.to_dict() for v in self.default_values]

return payload

Expand Down Expand Up @@ -512,6 +521,79 @@ def default(self) -> Optional[str]:
return self.value


class SelectDefaultValue:
"""Represents a select menu's default value.
These can be created by users.
.. versionadded:: 2.4
Parameters
-----------
id: :class:`int`
The id of a role, user, or channel.
type: :class:`SelectDefaultValueType`
The type of value that ``id`` represents.
"""

def __init__(
self,
*,
id: int,
type: SelectDefaultValueType,
) -> None:
self.id: int = id
self._type: SelectDefaultValueType = type

@property
def type(self) -> SelectDefaultValueType:
return self._type

@type.setter
def type(self, value: SelectDefaultValueType) -> None:
if not isinstance(value, SelectDefaultValueType):
raise TypeError(f'expected SelectDefaultValueType, received {value.__class__.__name__} instead')

self._type = value

def __repr__(self) -> str:
return f'<SelectDefaultValue id={self.id!r} type={self.type!r}>'

@classmethod
def from_dict(cls, data: SelectDefaultValuesPayload) -> SelectDefaultValue:
return cls(
id=data['id'],
type=try_enum(SelectDefaultValueType, data['type']),
)

def to_dict(self) -> SelectDefaultValuesPayload:
return {
'id': self.id,
'type': self._type.value,
}

@classmethod
def from_channel(cls, channel: Snowflake, /) -> Self:
return cls(
id=channel.id,
type=SelectDefaultValueType.channel,
)

@classmethod
def from_role(cls, role: Snowflake, /) -> Self:
return cls(
id=role.id,
type=SelectDefaultValueType.role,
)

@classmethod
def from_user(cls, user: Snowflake, /) -> Self:
return cls(
id=user.id,
type=SelectDefaultValueType.user,
)


@overload
def _component_factory(data: ActionRowChildComponentPayload) -> Optional[ActionRowChildComponentType]:
...
Expand Down
7 changes: 7 additions & 0 deletions discord/enums.py
Expand Up @@ -69,6 +69,7 @@
'AutoModRuleActionType',
'ForumLayoutType',
'ForumOrderType',
'SelectDefaultValueType',
)

if TYPE_CHECKING:
Expand Down Expand Up @@ -772,6 +773,12 @@ class ForumOrderType(Enum):
creation_date = 1


class SelectDefaultValueType(Enum):
user = 'user'
role = 'role'
channel = 'channel'


def create_unknown_value(cls: Type[E], val: Any) -> E:
value_cls = cls._enum_value_cls_ # type: ignore # This is narrowed below
name = f'unknown_{val}'
Expand Down
6 changes: 5 additions & 1 deletion discord/ext/commands/cog.py
Expand Up @@ -25,6 +25,7 @@

import inspect
import discord
import logging
from discord import app_commands
from discord.utils import maybe_coroutine, _to_kebab_case

Expand Down Expand Up @@ -65,6 +66,7 @@
FuncT = TypeVar('FuncT', bound=Callable[..., Any])

MISSING: Any = discord.utils.MISSING
_log = logging.getLogger(__name__)


class CogMeta(type):
Expand Down Expand Up @@ -360,6 +362,8 @@ def __new__(cls, *args: Any, **kwargs: Any) -> Self:
if isinstance(app_command, app_commands.Group):
for child in app_command.walk_commands():
app_command_refs[child.qualified_name] = child
if hasattr(child, '__commands_is_hybrid_app_command__') and child.qualified_name in lookup:
child.wrapped = lookup[child.qualified_name] # type: ignore

if self.__cog_app_commands_group__:
children.append(app_command) # type: ignore # Somehow it thinks it can be None here
Expand Down Expand Up @@ -769,7 +773,7 @@ async def _eject(self, bot: BotBase, guild_ids: Optional[Iterable[int]]) -> None
try:
await maybe_coroutine(self.cog_unload)
except Exception:
pass
_log.exception('Ignoring exception in cog unload for Cog %r (%r)', cls, self.qualified_name)


class GroupCog(Cog):
Expand Down

0 comments on commit 283cf24

Please sign in to comment.