Browse Source

merge master

pull/10166/head
DA-344 4 weeks ago
parent
commit
d5d3348518
  1. 4
      discord/activity.py
  2. 6
      discord/app_commands/installs.py
  3. 6
      discord/app_commands/models.py
  4. 8
      discord/app_commands/transformers.py
  5. 6
      discord/client.py
  6. 83
      discord/colour.py
  7. 3
      discord/components.py
  8. 8
      discord/embeds.py
  9. 8
      discord/ext/commands/context.py
  10. 3
      discord/ext/commands/parameters.py
  11. 5
      discord/guild.py
  12. 7
      discord/http.py
  13. 11
      discord/interactions.py
  14. 3
      discord/message.py
  15. 10
      discord/permissions.py
  16. 4
      discord/shard.py
  17. 6
      discord/template.py
  18. 1
      discord/types/interactions.py
  19. 3
      discord/ui/view.py
  20. 6
      docs/api.rst
  21. 2
      tests/test_app_commands_invoke.py
  22. 13
      tests/test_colour.py
  23. 11
      tests/test_embed.py

4
discord/activity.py

@ -418,7 +418,7 @@ class Game(BaseActivity):
return str(self.name)
def __repr__(self) -> str:
return f'<Game name={self.name!r}>'
return f'<Game name={self.name!r} platform={self.platform!r}>'
def to_dict(self) -> Dict[str, Any]:
timestamps: Dict[str, Any] = {}
@ -514,7 +514,7 @@ class Streaming(BaseActivity):
return str(self.name)
def __repr__(self) -> str:
return f'<Streaming name={self.name!r}>'
return f'<Streaming name={self.name!r} platform={self.platform!r}>'
@property
def twitch_name(self) -> Optional[str]:

6
discord/app_commands/installs.py

@ -57,6 +57,9 @@ class AppInstallationType:
self._guild: Optional[bool] = guild
self._user: Optional[bool] = user
def __repr__(self):
return f'<AppInstallationType guild={self.guild!r} user={self.user!r}>'
@property
def guild(self) -> bool:
""":class:`bool`: Whether the integration is a guild install."""
@ -142,6 +145,9 @@ class AppCommandContext:
self._dm_channel: Optional[bool] = dm_channel
self._private_channel: Optional[bool] = private_channel
def __repr__(self) -> str:
return f'<AppCommandContext guild={self.guild!r} dm_channel={self.dm_channel!r} private_channel={self.private_channel!r}>'
@property
def guild(self) -> bool:
""":class:`bool`: Whether the context allows usage in a guild."""

6
discord/app_commands/models.py

@ -1063,6 +1063,9 @@ class AppCommandPermissions:
self.target: Union[Object, User, Member, Role, AllChannels, GuildChannel] = _object
def __repr__(self) -> str:
return f'<AppCommandPermissions id={self.id} type={self.type!r} guild={self.guild!r} permission={self.permission}>'
def to_dict(self) -> ApplicationCommandPermissions:
return {
'id': self.target.id,
@ -1106,6 +1109,9 @@ class GuildAppCommandPermissions:
AppCommandPermissions(data=value, guild=guild, state=self._state) for value in data['permissions']
]
def __repr__(self) -> str:
return f'<GuildAppCommandPermissions id={self.id!r} guild_id={self.guild_id!r} permissions={self.permissions!r}>'
def to_dict(self) -> Dict[str, Any]:
return {'permissions': [p.to_dict() for p in self.permissions]}

8
discord/app_commands/transformers.py

@ -52,7 +52,7 @@ from ..channel import StageChannel, VoiceChannel, TextChannel, CategoryChannel,
from ..abc import GuildChannel
from ..threads import Thread
from ..enums import Enum as InternalEnum, AppCommandOptionType, ChannelType, Locale
from ..utils import MISSING, maybe_coroutine
from ..utils import MISSING, maybe_coroutine, _human_join
from ..user import User
from ..role import Role
from ..member import Member
@ -631,7 +631,7 @@ class BaseChannelTransformer(Transformer[ClientT]):
display_name = channel_types[0].__name__
types = CHANNEL_TO_TYPES[channel_types[0]]
else:
display_name = '{}, and {}'.format(', '.join(t.__name__ for t in channel_types[:-1]), channel_types[-1].__name__)
display_name = _human_join([t.__name__ for t in channel_types])
types = []
for t in channel_types:
@ -689,6 +689,7 @@ CHANNEL_TO_TYPES: Dict[Any, List[ChannelType]] = {
ChannelType.news,
ChannelType.category,
ChannelType.forum,
ChannelType.media,
],
GuildChannel: [
ChannelType.stage_voice,
@ -697,6 +698,7 @@ CHANNEL_TO_TYPES: Dict[Any, List[ChannelType]] = {
ChannelType.news,
ChannelType.category,
ChannelType.forum,
ChannelType.media,
],
AppCommandThread: [ChannelType.news_thread, ChannelType.private_thread, ChannelType.public_thread],
Thread: [ChannelType.news_thread, ChannelType.private_thread, ChannelType.public_thread],
@ -704,7 +706,7 @@ CHANNEL_TO_TYPES: Dict[Any, List[ChannelType]] = {
VoiceChannel: [ChannelType.voice],
TextChannel: [ChannelType.text, ChannelType.news],
CategoryChannel: [ChannelType.category],
ForumChannel: [ChannelType.forum],
ForumChannel: [ChannelType.forum, ChannelType.media],
}
BUILT_IN_TRANSFORMERS: Dict[Any, Transformer] = {

6
discord/client.py

@ -67,7 +67,7 @@ from .voice_client import VoiceClient
from .http import HTTPClient
from .state import ConnectionState
from . import utils
from .utils import MISSING, time_snowflake
from .utils import MISSING, time_snowflake, deprecated
from .object import Object
from .backoff import ExponentialBackoff
from .webhook import Webhook
@ -2388,6 +2388,7 @@ class Client:
data = await self.http.get_guild_preview(guild_id)
return GuildPreview(data=data, state=self._connection)
@deprecated()
async def create_guild(
self,
*,
@ -2408,6 +2409,9 @@ class Client:
This function will now raise :exc:`ValueError` instead of
``InvalidArgument``.
.. deprecated:: 2.6
This function is deprecated and will be removed in a future version.
Parameters
----------
name: :class:`str`

83
discord/colour.py

@ -21,6 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from __future__ import annotations
import colorsys
@ -457,20 +458,59 @@ class Colour:
"""
return cls(0x99AAB5)
@classmethod
def ash_theme(cls) -> Self:
"""A factory method that returns a :class:`Colour` with a value of ``0x2E2E34``.
This will appear transparent on Discord's ash theme.
.. colour:: #2E2E34
.. versionadded:: 2.6
"""
return cls(0x2E2E34)
@classmethod
def dark_theme(cls) -> Self:
"""A factory method that returns a :class:`Colour` with a value of ``0x313338``.
"""A factory method that returns a :class:`Colour` with a value of ``0x1A1A1E``.
This will appear transparent on Discord's dark theme.
.. colour:: #313338
.. colour:: #1A1A1E
.. versionadded:: 1.5
.. versionchanged:: 2.2
Updated colour from previous ``0x36393F`` to reflect discord theme changes.
.. versionchanged:: 2.6
Updated colour from previous ``0x313338`` to reflect discord theme changes.
"""
return cls(0x313338)
return cls(0x1A1A1E)
@classmethod
def onyx_theme(cls) -> Self:
"""A factory method that returns a :class:`Colour` with a value of ``0x070709``.
This will appear transparent on Discord's onyx theme.
.. colour:: #070709
.. versionadded:: 2.6
"""
return cls(0x070709)
@classmethod
def light_theme(cls) -> Self:
"""A factory method that returns a :class:`Colour` with a value of ``0xFBFBFB``.
This will appear transparent on Discord's light theme.
.. colour:: #FBFBFB
.. versionadded:: 2.6
"""
return cls(0xFBFBFB)
@classmethod
def fuchsia(cls) -> Self:
@ -492,25 +532,52 @@ class Colour:
"""
return cls(0xFEE75C)
@classmethod
def ash_embed(cls) -> Self:
"""A factory method that returns a :class:`Colour` with a value of ``0x37373E``.
.. colour:: #37373E
.. versionadded:: 2.6
"""
return cls(0x37373E)
@classmethod
def dark_embed(cls) -> Self:
"""A factory method that returns a :class:`Colour` with a value of ``0x2B2D31``.
"""A factory method that returns a :class:`Colour` with a value of ``0x242429``.
.. colour:: #2B2D31
.. colour:: #242429
.. versionadded:: 2.2
.. versionchanged:: 2.6
Updated colour from previous ``0x2B2D31`` to reflect discord theme changes.
"""
return cls(0x242429)
@classmethod
def onyx_embed(cls) -> Self:
"""A factory method that returns a :class:`Colour` with a value of ``0x131416``.
.. colour:: #131416
.. versionadded:: 2.6
"""
return cls(0x2B2D31)
return cls(0x131416)
@classmethod
def light_embed(cls) -> Self:
"""A factory method that returns a :class:`Colour` with a value of ``0xEEEFF1``.
"""A factory method that returns a :class:`Colour` with a value of ``0xFFFFFF``.
.. colour:: #EEEFF1
.. versionadded:: 2.2
.. versionchanged:: 2.6
Updated colour from previous ``0xEEEFF1`` to reflect discord theme changes.
"""
return cls(0xEEEFF1)
return cls(0xFFFFFF)
@classmethod
def pink(cls) -> Self:

3
discord/components.py

@ -527,6 +527,9 @@ class SelectOption:
return payload
def copy(self) -> SelectOption:
return self.__class__.from_dict(self.to_dict())
class TextInput(Component):
"""Represents a text input from the Discord Bot UI Kit.

8
discord/embeds.py

@ -61,6 +61,12 @@ class EmbedMediaProxy(EmbedProxy):
super().__init__(layer)
self._flags = self.__dict__.pop('flags', 0)
def __bool__(self) -> bool:
# This is a nasty check to see if we only have the `_flags` attribute which is created regardless in init.
# Had we had any of the other items, like image/video data this would be >1 and therefor
# would not be "empty".
return len(self.__dict__) > 1
@property
def flags(self) -> AttachmentFlags:
return AttachmentFlags._from_value(self._flags or 0)
@ -737,7 +743,7 @@ class Embed:
# fmt: off
result = {
key[1:]: getattr(self, key)
for key in self.__slots__
for key in Embed.__slots__
if key[0] == '_' and hasattr(self, key)
}
# fmt: on

8
discord/ext/commands/context.py

@ -992,7 +992,7 @@ class Context(discord.abc.Messageable, Generic[BotT]):
suppress_embeds: bool = False,
ephemeral: bool = False,
silent: bool = False,
poll: Poll = MISSING,
poll: Optional[Poll] = None,
) -> Message:
"""|coro|
@ -1084,10 +1084,12 @@ class Context(discord.abc.Messageable, Generic[BotT]):
.. versionadded:: 2.2
poll: :class:`~discord.Poll`
poll: Optional[:class:`~discord.Poll`]
The poll to send with this message.
.. versionadded:: 2.4
.. versionchanged:: 2.6
This can now be ``None`` and defaults to ``None`` instead of ``MISSING``.
Raises
--------
@ -1142,7 +1144,7 @@ class Context(discord.abc.Messageable, Generic[BotT]):
'suppress_embeds': suppress_embeds,
'ephemeral': ephemeral,
'silent': silent,
'poll': poll,
'poll': MISSING if poll is None else poll,
}
if self.interaction.response.is_done():

3
discord/ext/commands/parameters.py

@ -109,6 +109,9 @@ class Parameter(inspect.Parameter):
self._fallback = False
self._displayed_name = displayed_name
def __repr__(self) -> str:
return f'<{self.__class__.__name__} name={self._name!r} required={self.required}>'
def replace(
self,
*,

5
discord/guild.py

@ -2921,6 +2921,11 @@ class Guild(Hashable):
The name of the template.
description: :class:`str`
The description of the template.
Returns
--------
:class:`Template`
The created template.
"""
from .template import Template

7
discord/http.py

@ -467,7 +467,12 @@ class Ratelimit:
future = self._loop.create_future()
self._pending_requests.append(future)
try:
await future
while not future.done():
# 30 matches the smallest allowed max_ratelimit_timeout
max_wait_time = self.expires - self._loop.time() if self.expires else 30
await asyncio.wait([future], timeout=max_wait_time)
if not future.done():
await self._refresh()
except:
future.cancel()
if self.remaining > 0 and not future.cancelled():

11
discord/interactions.py

@ -154,6 +154,10 @@ class Interaction(Generic[ClientT]):
The context of the interaction.
.. versionadded:: 2.4
filesize_limit: int
The maximum number of bytes a file can have when responding to this interaction.
.. versionadded:: 2.6
"""
__slots__: Tuple[str, ...] = (
@ -172,7 +176,8 @@ class Interaction(Generic[ClientT]):
'command_failed',
'entitlement_sku_ids',
'entitlements',
"context",
'context',
'filesize_limit',
'_integration_owners',
'_permissions',
'_app_permissions',
@ -214,6 +219,7 @@ class Interaction(Generic[ClientT]):
self.application_id: int = int(data['application_id'])
self.entitlement_sku_ids: List[int] = [int(x) for x in data.get('entitlement_skus', []) or []]
self.entitlements: List[Entitlement] = [Entitlement(self._state, x) for x in data.get('entitlements', [])]
self.filesize_limit: int = data['attachment_size_limit']
# This is not entirely useful currently, unsure how to expose it in a way that it is.
self._integration_owners: Dict[int, Snowflake] = {
int(k): int(v) for k, v in data.get('authorizing_integration_owners', {}).items()
@ -730,6 +736,9 @@ class InteractionCallbackResponse(Generic[ClientT]):
self.type: InteractionResponseType = type
self._update(data)
def __repr__(self) -> str:
return f'<InteractionCallbackResponse id={self.id} type={self.type!r}>'
def _update(self, data: InteractionCallbackPayload) -> None:
interaction = data['interaction']

3
discord/message.py

@ -988,6 +988,9 @@ class MessageApplication:
self._icon: Optional[str] = data['icon']
self._cover_image: Optional[str] = data.get('cover_image')
def __str__(self) -> str:
return self.name
def __repr__(self) -> str:
return f'<MessageApplication id={self.id} name={self.name!r}>'

10
discord/permissions.py

@ -363,6 +363,16 @@ class Permissions(BaseFlags):
"""
return cls(0b0000_0000_0000_0000_0000_0001_0000_0100_0111_0000_0000_0000_0010_0000_0011_1110)
@classmethod
def apps(cls) -> Self:
"""A factory method that creates a :class:`Permissions` with all
"Apps" permissions from the official Discord UI set to ``True``.
.. versionadded:: 2.6
"""
return cls(0b0000_0000_0000_0100_0000_0000_1000_0000_1000_0000_0000_0000_0000_0000_0000_0000)
@classmethod
def events(cls) -> Self:
"""A factory method that creates a :class:`Permissions` with all

4
discord/shard.py

@ -517,10 +517,10 @@ class AutoShardedClient(Client):
if item.type == EventType.close:
await self.close()
if isinstance(item.error, ConnectionClosed):
if item.error.code != 1000:
raise item.error
if item.error.code == 4014:
raise PrivilegedIntentsRequired(item.shard.id) from None
if item.error.code != 1000:
raise item.error
return
elif item.type in (EventType.identify, EventType.resume):
await item.shard.reidentify(item.error)

6
discord/template.py

@ -25,7 +25,7 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations
from typing import Any, Optional, TYPE_CHECKING, List
from .utils import parse_time, _bytes_to_base64_data, MISSING
from .utils import parse_time, _bytes_to_base64_data, MISSING, deprecated
from .guild import Guild
# fmt: off
@ -164,6 +164,7 @@ class Template:
f' creator={self.creator!r} source_guild={self.source_guild!r} is_dirty={self.is_dirty}>'
)
@deprecated()
async def create_guild(self, name: str, icon: bytes = MISSING) -> Guild:
"""|coro|
@ -178,6 +179,9 @@ class Template:
This function will now raise :exc:`ValueError` instead of
``InvalidArgument``.
.. deprecated:: 2.6
This function is deprecated and will be removed in a future version.
Parameters
----------
name: :class:`str`

1
discord/types/interactions.py

@ -233,6 +233,7 @@ class _BaseInteraction(TypedDict):
entitlements: NotRequired[List[Entitlement]]
authorizing_integration_owners: Dict[Literal['0', '1'], Snowflake]
context: NotRequired[InteractionContextType]
attachment_size_limit: int
class PingInteraction(_BaseInteraction):

3
discord/ui/view.py

@ -51,6 +51,7 @@ import os
import copy
from .item import Item, ItemCallbackType
from .select import Select
from .dynamic import DynamicItem
from ..components import (
Component,
@ -385,6 +386,8 @@ class BaseView:
item: Item = raw.__discord_ui_model_type__(**raw.__discord_ui_model_kwargs__)
item.callback = _ViewCallback(raw, self, item) # type: ignore
item._view = self
if isinstance(item, Select):
item.options = [option.copy() for option in item.options]
setattr(self, raw.__name__, item)
parent = getattr(raw, '__discord_ui_parent__', None)
if parent:

6
docs/api.rst

@ -1354,8 +1354,10 @@ Soundboard
.. versionadded:: 2.5
:param sound: The soundboard sound that was updated.
:type sound: :class:`SoundboardSound`
:param before: The soundboard sound before the update.
:type before: :class:`SoundboardSound`
:param after: The soundboard sound after the update.
:type after: :class:`SoundboardSound`
Stages

2
tests/test_app_commands_invoke.py

@ -21,6 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from __future__ import annotations
@ -90,6 +91,7 @@ class MockCommandInteraction(discord.Interaction):
"version": 1,
"type": 2,
"data": self._get_command_data(command, self._get_command_options(**options)),
"attachment_size_limit": 0,
}
super().__init__(data=data, state=client._connection)

13
tests/test_colour.py

@ -106,11 +106,16 @@ def test_from_str_failures(value):
(discord.Colour.og_blurple(), 0x7289DA),
(discord.Colour.blurple(), 0x5865F2),
(discord.Colour.greyple(), 0x99AAB5),
(discord.Colour.dark_theme(), 0x313338),
(discord.Colour.ash_theme(), 0x2E2E34),
(discord.Colour.dark_theme(), 0x1A1A1E),
(discord.Colour.onyx_theme(), 0x070709),
(discord.Colour.light_theme(), 0xFBFBFB),
(discord.Colour.fuchsia(), 0xEB459E),
(discord.Colour.yellow(), 0xFEE75C),
(discord.Colour.dark_embed(), 0x2B2D31),
(discord.Colour.light_embed(), 0xEEEFF1),
(discord.Colour.ash_embed(), 0x37373E),
(discord.Colour.dark_embed(), 0x242429),
(discord.Colour.onyx_embed(), 0x131416),
(discord.Colour.light_embed(), 0xFFFFFF),
(discord.Colour.pink(), 0xEB459F),
],
)
@ -118,8 +123,6 @@ def test_static_colours(value, expected):
assert value.value == expected
@pytest.mark.parametrize(
('value', 'property', 'expected'),
[

11
tests/test_embed.py

@ -267,3 +267,14 @@ def test_embed_colour_setter_failure(value):
embed = discord.Embed()
with pytest.raises(TypeError):
embed.colour = value
@pytest.mark.parametrize(
('title', 'return_val'),
[
('test', True),
(None, False)
]
)
def test_embed_truthiness(title: str, return_val: bool) -> None:
embed = discord.Embed(title=title)
assert bool(embed) is return_val

Loading…
Cancel
Save