Skip to content

Commit 283cf24

Browse files
authored
Merge branch 'Rapptz:master' into guide/slash-commands
2 parents 2c7bc56 + e1aa6cc commit 283cf24

35 files changed

+1327
-363
lines changed

discord/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ async def bot_check_once(self, ctx):
157157
async def cog_command_error(self, ctx, error):
158158
# error handling to every command in here
159159
pass
160-
160+
161161
async def cog_app_command_error(self, interaction, error):
162162
# error handling to every application command in here
163163
pass

discord/abc.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1842,7 +1842,7 @@ def _get_voice_state_pair(self) -> Tuple[int, int]:
18421842
async def connect(
18431843
self,
18441844
*,
1845-
timeout: float = 60.0,
1845+
timeout: float = 30.0,
18461846
reconnect: bool = True,
18471847
cls: Callable[[Client, Connectable], T] = VoiceClient,
18481848
self_deaf: bool = False,
@@ -1858,7 +1858,7 @@ async def connect(
18581858
Parameters
18591859
-----------
18601860
timeout: :class:`float`
1861-
The timeout in seconds to wait for the voice endpoint.
1861+
The timeout in seconds to wait the connection to complete.
18621862
reconnect: :class:`bool`
18631863
Whether the bot should automatically attempt
18641864
a reconnect if a part of the handshake fails

discord/activity.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,9 @@ class CustomActivity(BaseActivity):
732732

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

735-
def __init__(self, name: Optional[str], *, emoji: Optional[PartialEmoji] = None, **extra: Any) -> None:
735+
def __init__(
736+
self, name: Optional[str], *, emoji: Optional[Union[PartialEmoji, Dict[str, Any], str]] = None, **extra: Any
737+
) -> None:
736738
super().__init__(**extra)
737739
self.name: Optional[str] = name
738740
self.state: Optional[str] = extra.pop('state', name)

discord/app_commands/commands.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1548,6 +1548,9 @@ def __init__(
15481548
if not self.description:
15491549
raise TypeError('groups must have a description')
15501550

1551+
if not self.name:
1552+
raise TypeError('groups must have a name')
1553+
15511554
self.parent: Optional[Group] = parent
15521555
self.module: Optional[str]
15531556
if cls.__discord_app_commands_has_module__:

discord/app_commands/errors.py

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
from ..enums import AppCommandOptionType, AppCommandType, Locale
3030
from ..errors import DiscordException, HTTPException, _flatten_error_dict
31+
from ..utils import _human_join
3132

3233
__all__ = (
3334
'AppCommandError',
@@ -242,13 +243,7 @@ class MissingAnyRole(CheckFailure):
242243
def __init__(self, missing_roles: SnowflakeList) -> None:
243244
self.missing_roles: SnowflakeList = missing_roles
244245

245-
missing = [f"'{role}'" for role in missing_roles]
246-
247-
if len(missing) > 2:
248-
fmt = '{}, or {}'.format(', '.join(missing[:-1]), missing[-1])
249-
else:
250-
fmt = ' or '.join(missing)
251-
246+
fmt = _human_join([f"'{role}'" for role in missing_roles])
252247
message = f'You are missing at least one of the required roles: {fmt}'
253248
super().__init__(message)
254249

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

273268
missing = [perm.replace('_', ' ').replace('guild', 'server').title() for perm in missing_permissions]
274-
275-
if len(missing) > 2:
276-
fmt = '{}, and {}'.format(", ".join(missing[:-1]), missing[-1])
277-
else:
278-
fmt = ' and '.join(missing)
269+
fmt = _human_join(missing, final='and')
279270
message = f'You are missing {fmt} permission(s) to run this command.'
280271
super().__init__(message, *args)
281272

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

300291
missing = [perm.replace('_', ' ').replace('guild', 'server').title() for perm in missing_permissions]
301-
302-
if len(missing) > 2:
303-
fmt = '{}, and {}'.format(", ".join(missing[:-1]), missing[-1])
304-
else:
305-
fmt = ' and '.join(missing)
292+
fmt = _human_join(missing, final='and')
306293
message = f'Bot requires {fmt} permission(s) to run this command.'
307294
super().__init__(message, *args)
308295

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

532519
if self._errors:
533-
for index, inner in self._errors.items():
534-
_get_command_error(index, inner, commands, messages)
520+
# Handle case where the errors dict has no actual chain such as APPLICATION_COMMAND_TOO_LARGE
521+
if len(self._errors) == 1 and '_errors' in self._errors:
522+
errors = self._errors['_errors']
523+
if len(errors) == 1:
524+
extra = errors[0].get('message')
525+
if extra:
526+
messages[0] += f': {extra}'
527+
else:
528+
messages.extend(f'Error {e.get("code", "")}: {e.get("message", "")}' for e in errors)
529+
else:
530+
for index, inner in self._errors.items():
531+
_get_command_error(index, inner, commands, messages)
535532

536533
# Equivalent to super().__init__(...) but skips other constructors
537534
self.args = ('\n'.join(messages),)

discord/app_commands/transformers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,7 @@ class Transform:
525525
.. versionadded:: 2.0
526526
"""
527527

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

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

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

discord/app_commands/tree.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1240,7 +1240,7 @@ async def _call(self, interaction: Interaction[ClientT]) -> None:
12401240
await command._invoke_autocomplete(interaction, focused, namespace)
12411241
except Exception:
12421242
# Suppress exception since it can't be handled anyway.
1243-
pass
1243+
_log.exception('Ignoring exception in autocomplete for %r', command.qualified_name)
12441244

12451245
return
12461246

discord/channel.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1600,6 +1600,7 @@ async def create_instance(
16001600
topic: str,
16011601
privacy_level: PrivacyLevel = MISSING,
16021602
send_start_notification: bool = False,
1603+
scheduled_event: Snowflake = MISSING,
16031604
reason: Optional[str] = None,
16041605
) -> StageInstance:
16051606
"""|coro|
@@ -1621,6 +1622,10 @@ async def create_instance(
16211622
You must have :attr:`~Permissions.mention_everyone` to do this.
16221623
16231624
.. versionadded:: 2.3
1625+
scheduled_event: :class:`~discord.abc.Snowflake`
1626+
The guild scheduled event associated with the stage instance.
1627+
1628+
.. versionadded:: 2.4
16241629
reason: :class:`str`
16251630
The reason the stage instance was created. Shows up on the audit log.
16261631
@@ -1647,6 +1652,9 @@ async def create_instance(
16471652

16481653
payload['privacy_level'] = privacy_level.value
16491654

1655+
if scheduled_event is not MISSING:
1656+
payload['guild_scheduled_event_id'] = scheduled_event.id
1657+
16501658
payload['send_start_notification'] = send_start_notification
16511659

16521660
data = await self._state.http.create_stage_instance(**payload, reason=reason)

discord/components.py

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from __future__ import annotations
2626

2727
from typing import ClassVar, List, Literal, Optional, TYPE_CHECKING, Tuple, Union, overload
28-
from .enums import try_enum, ComponentType, ButtonStyle, TextStyle, ChannelType
28+
from .enums import try_enum, ComponentType, ButtonStyle, TextStyle, ChannelType, SelectDefaultValueType
2929
from .utils import get_slots, MISSING
3030
from .partial_emoji import PartialEmoji, _EmojiTag
3131

@@ -40,8 +40,10 @@
4040
ActionRow as ActionRowPayload,
4141
TextInput as TextInputPayload,
4242
ActionRowChildComponent as ActionRowChildComponentPayload,
43+
SelectDefaultValues as SelectDefaultValuesPayload,
4344
)
4445
from .emoji import Emoji
46+
from .abc import Snowflake
4547

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

@@ -53,6 +55,7 @@
5355
'SelectMenu',
5456
'SelectOption',
5557
'TextInput',
58+
'SelectDefaultValue',
5659
)
5760

5861

@@ -263,6 +266,7 @@ class SelectMenu(Component):
263266
'options',
264267
'disabled',
265268
'channel_types',
269+
'default_values',
266270
)
267271

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

280287
def to_dict(self) -> SelectMenuPayload:
281288
payload: SelectMenuPayload = {
@@ -291,6 +298,8 @@ def to_dict(self) -> SelectMenuPayload:
291298
payload['options'] = [op.to_dict() for op in self.options]
292299
if self.channel_types:
293300
payload['channel_types'] = [t.value for t in self.channel_types]
301+
if self.default_values:
302+
payload["default_values"] = [v.to_dict() for v in self.default_values]
294303

295304
return payload
296305

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

514523

524+
class SelectDefaultValue:
525+
"""Represents a select menu's default value.
526+
527+
These can be created by users.
528+
529+
.. versionadded:: 2.4
530+
531+
Parameters
532+
-----------
533+
id: :class:`int`
534+
The id of a role, user, or channel.
535+
type: :class:`SelectDefaultValueType`
536+
The type of value that ``id`` represents.
537+
"""
538+
539+
def __init__(
540+
self,
541+
*,
542+
id: int,
543+
type: SelectDefaultValueType,
544+
) -> None:
545+
self.id: int = id
546+
self._type: SelectDefaultValueType = type
547+
548+
@property
549+
def type(self) -> SelectDefaultValueType:
550+
return self._type
551+
552+
@type.setter
553+
def type(self, value: SelectDefaultValueType) -> None:
554+
if not isinstance(value, SelectDefaultValueType):
555+
raise TypeError(f'expected SelectDefaultValueType, received {value.__class__.__name__} instead')
556+
557+
self._type = value
558+
559+
def __repr__(self) -> str:
560+
return f'<SelectDefaultValue id={self.id!r} type={self.type!r}>'
561+
562+
@classmethod
563+
def from_dict(cls, data: SelectDefaultValuesPayload) -> SelectDefaultValue:
564+
return cls(
565+
id=data['id'],
566+
type=try_enum(SelectDefaultValueType, data['type']),
567+
)
568+
569+
def to_dict(self) -> SelectDefaultValuesPayload:
570+
return {
571+
'id': self.id,
572+
'type': self._type.value,
573+
}
574+
575+
@classmethod
576+
def from_channel(cls, channel: Snowflake, /) -> Self:
577+
return cls(
578+
id=channel.id,
579+
type=SelectDefaultValueType.channel,
580+
)
581+
582+
@classmethod
583+
def from_role(cls, role: Snowflake, /) -> Self:
584+
return cls(
585+
id=role.id,
586+
type=SelectDefaultValueType.role,
587+
)
588+
589+
@classmethod
590+
def from_user(cls, user: Snowflake, /) -> Self:
591+
return cls(
592+
id=user.id,
593+
type=SelectDefaultValueType.user,
594+
)
595+
596+
515597
@overload
516598
def _component_factory(data: ActionRowChildComponentPayload) -> Optional[ActionRowChildComponentType]:
517599
...

discord/enums.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
'AutoModRuleActionType',
7070
'ForumLayoutType',
7171
'ForumOrderType',
72+
'SelectDefaultValueType',
7273
)
7374

7475
if TYPE_CHECKING:
@@ -772,6 +773,12 @@ class ForumOrderType(Enum):
772773
creation_date = 1
773774

774775

776+
class SelectDefaultValueType(Enum):
777+
user = 'user'
778+
role = 'role'
779+
channel = 'channel'
780+
781+
775782
def create_unknown_value(cls: Type[E], val: Any) -> E:
776783
value_cls = cls._enum_value_cls_ # type: ignore # This is narrowed below
777784
name = f'unknown_{val}'

0 commit comments

Comments
 (0)