Browse Source

Fix issues with previous upstream commits

pull/10109/head
dolfies 2 years ago
parent
commit
8a1a3ffcce
  1. 2
      .github/workflows/lint.yml
  2. 1
      discord/abc.py
  3. 34
      discord/activity.py
  4. 9
      discord/application.py
  5. 2
      discord/automod.py
  6. 13
      discord/calls.py
  7. 21
      discord/channel.py
  8. 12
      discord/client.py
  9. 48
      discord/components.py
  10. 2
      discord/entitlements.py
  11. 2
      discord/ext/commands/converter.py
  12. 19
      discord/ext/commands/cooldowns.py
  13. 9
      discord/ext/commands/core.py
  14. 18
      discord/flags.py
  15. 1
      discord/gateway.py
  16. 30
      discord/guild.py
  17. 9
      discord/http.py
  18. 2
      discord/invite.py
  19. 21
      discord/message.py
  20. 4
      discord/opus.py
  21. 16
      discord/player.py
  22. 5
      discord/profile.py
  23. 12
      discord/scheduled_event.py
  24. 71
      discord/settings.py
  25. 2
      discord/state.py
  26. 2
      discord/team.py
  27. 20
      discord/threads.py
  28. 6
      discord/types/message.py
  29. 18
      discord/webhook/async_.py
  30. 12
      discord/webhook/sync.py
  31. 8
      discord/welcome_screen.py
  32. 29
      docs/api.rst
  33. 2
      docs/faq.rst
  34. 4
      docs/migrating.rst
  35. 12
      setup.py

2
.github/workflows/lint.yml

@ -35,7 +35,7 @@ jobs:
with: with:
version: '1.1.289' version: '1.1.289'
warnings: false warnings: false
no-comments: ${{ matrix.python-version != '3.x' }} no-comments: false
- name: Run black - name: Run black
if: ${{ always() && steps.install-deps.outcome == 'success' }} if: ${{ always() && steps.install-deps.outcome == 'success' }}

1
discord/abc.py

@ -90,6 +90,7 @@ if TYPE_CHECKING:
VocalGuildChannel, VocalGuildChannel,
VoiceChannel, VoiceChannel,
StageChannel, StageChannel,
CategoryChannel,
) )
from .threads import Thread from .threads import Thread
from .enums import InviteTarget from .enums import InviteTarget

34
discord/activity.py

@ -836,42 +836,42 @@ class CustomActivity(BaseActivity):
return ActivityType.custom return ActivityType.custom
def to_dict(self) -> ActivityPayload: def to_dict(self) -> ActivityPayload:
o = { payload = {
'type': ActivityType.custom.value, 'type': ActivityType.custom.value,
'state': self.name, 'state': self.name,
'name': 'Custom Status', # Not a confusing API at all 'name': 'Custom Status', # Not a confusing API at all
} }
if self.emoji: if self.emoji:
o['emoji'] = self.emoji.to_dict() payload['emoji'] = self.emoji.to_dict()
return o # type: ignore return payload # type: ignore
def to_legacy_settings_dict(self) -> Dict[str, Any]: def to_legacy_settings_dict(self) -> Dict[str, Optional[Union[str, int]]]:
o: Dict[str, Optional[Union[str, int]]] = {} payload: Dict[str, Optional[Union[str, int]]] = {}
if self.name: if self.name:
o['text'] = self.name payload['text'] = self.name
if self.emoji: if self.emoji:
emoji = self.emoji emoji = self.emoji
o['emoji_name'] = emoji.name payload['emoji_name'] = emoji.name
if emoji.id: if emoji.id:
o['emoji_id'] = emoji.id payload['emoji_id'] = emoji.id
if self.expires_at is not None: if self.expires_at is not None:
o['expires_at'] = self.expires_at.isoformat() payload['expires_at'] = self.expires_at.isoformat()
return o return payload
def to_settings_dict(self) -> Dict[str, Any]: def to_settings_dict(self) -> Dict[str, Optional[Union[str, int]]]:
o: Dict[str, Optional[Union[str, int]]] = {} payload: Dict[str, Optional[Union[str, int]]] = {}
if self.name: if self.name:
o['text'] = self.name payload['text'] = self.name
if self.emoji: if self.emoji:
emoji = self.emoji emoji = self.emoji
o['emoji_name'] = emoji.name payload['emoji_name'] = emoji.name
if emoji.id: if emoji.id:
o['emoji_id'] = emoji.id payload['emoji_id'] = emoji.id
if self.expires_at is not None: if self.expires_at is not None:
o['expires_at_ms'] = int(self.expires_at.timestamp() * 1000) payload['expires_at_ms'] = int(self.expires_at.timestamp() * 1000)
return o return payload
def __eq__(self, other: object) -> bool: def __eq__(self, other: object) -> bool:
return isinstance(other, CustomActivity) and other.name == self.name and other.emoji == self.emoji return isinstance(other, CustomActivity) and other.name == self.name and other.emoji == self.emoji

9
discord/application.py

@ -772,8 +772,7 @@ class ApplicationInstallParams:
application_id: :class:`int` application_id: :class:`int`
The ID of the application to be authorized. The ID of the application to be authorized.
scopes: List[:class:`str`] scopes: List[:class:`str`]
The list of `OAuth2 scopes <https://discord.com/developers/docs/topics/oauth2#shared-resources-oauth2-scopes>`_ The list of :ddocs:`OAuth2 scopes <topics/oauth2#shared-resources-oauth2-scopes>` to add the application with.
to add the application with.
permissions: :class:`Permissions` permissions: :class:`Permissions`
The permissions to grant to the added bot. The permissions to grant to the added bot.
""" """
@ -1652,7 +1651,7 @@ class PartialApplication(Hashable):
A list of RPC origin URLs, if RPC is enabled. A list of RPC origin URLs, if RPC is enabled.
verify_key: :class:`str` verify_key: :class:`str`
The hex encoded key for verification in interactions and the The hex encoded key for verification in interactions and the
GameSDK's `GetTicket <https://discord.com/developers/docs/game-sdk/applications#getticket>`_. GameSDK's :ddocs:`GetTicket <game-sdk/applications#getticket`.
terms_of_service_url: Optional[:class:`str`] terms_of_service_url: Optional[:class:`str`]
The application's terms of service URL, if set. The application's terms of service URL, if set.
privacy_policy_url: Optional[:class:`str`] privacy_policy_url: Optional[:class:`str`]
@ -2935,7 +2934,7 @@ class Application(PartialApplication):
_state = self._state _state = self._state
async def _after_strategy(retrieve: int, after: Optional[Snowflake], limit: Optional[Snowflake]): async def _after_strategy(retrieve: int, after: Optional[Snowflake], limit: Optional[int]):
after_id = after.id if after else None after_id = after.id if after else None
data = await _state.http.get_app_entitlements( data = await _state.http.get_app_entitlements(
self.id, self.id,
@ -2956,7 +2955,7 @@ class Application(PartialApplication):
return data, after, limit return data, after, limit
async def _before_strategy(retrieve: int, before: Optional[Snowflake], limit: Optional[Snowflake]): async def _before_strategy(retrieve: int, before: Optional[Snowflake], limit: Optional[int]):
before_id = before.id if before else None before_id = before.id if before else None
data = await _state.http.get_app_entitlements( data = await _state.http.get_app_entitlements(
self.id, self.id,

2
discord/automod.py

@ -519,12 +519,10 @@ class AutoModAction:
The ID of the system message that was sent to the predefined alert channel. The ID of the system message that was sent to the predefined alert channel.
content: :class:`str` content: :class:`str`
The content of the message that triggered the rule. The content of the message that triggered the rule.
Requires the :attr:`Intents.message_content` or it will always return an empty string.
matched_keyword: Optional[:class:`str`] matched_keyword: Optional[:class:`str`]
The matched keyword from the triggering message. The matched keyword from the triggering message.
matched_content: Optional[:class:`str`] matched_content: Optional[:class:`str`]
The matched content from the triggering message. The matched content from the triggering message.
Requires the :attr:`Intents.message_content` or it will always return ``None``.
""" """
__slots__ = ( __slots__ = (

13
discord/calls.py

@ -76,7 +76,7 @@ class CallMessage:
__slots__ = ('message', 'ended_timestamp', 'participants') __slots__ = ('message', 'ended_timestamp', 'participants')
def __init__(self, message: Message, *, participants: List[User], ended_timestamp: str) -> None: def __init__(self, message: Message, *, participants: List[User], ended_timestamp: Optional[str]) -> None:
self.message = message self.message = message
self.ended_timestamp = utils.parse_time(ended_timestamp) self.ended_timestamp = utils.parse_time(ended_timestamp)
self.participants = participants self.participants = participants
@ -188,8 +188,8 @@ class PrivateCall:
return list(self._ringing) return list(self._ringing)
@property @property
def initiator(self) -> User: def initiator(self) -> Optional[User]:
""":class:`.abc.User`: Returns the user that started the call.""" """Optional[:class:`.abc.User`]: Returns the user that started the call. Returns ``None`` if the message is not cached."""
return getattr(self.message, 'author', None) return getattr(self.message, 'author', None)
@property @property
@ -211,10 +211,9 @@ class PrivateCall:
} }
@cached_slot_property('_cs_message') @cached_slot_property('_cs_message')
def message(self) -> Message: def message(self) -> Optional[Message]:
""":class:`Message`: The message associated with this call.""" """Optional[:class:`Message`]: The message associated with this call. Sometimes may not be cached."""
# Lying to the type checker for better developer UX, very unlikely for the message to not be received return self._state._get_message(self._message_id)
return self._state._get_message(self._message_id) # type: ignore
async def fetch_message(self) -> Message: async def fetch_message(self) -> Message:
"""|coro| """|coro|

21
discord/channel.py

@ -59,6 +59,8 @@ from .threads import Thread
from .partial_emoji import _EmojiTag, PartialEmoji from .partial_emoji import _EmojiTag, PartialEmoji
from .flags import ChannelFlags from .flags import ChannelFlags
from .http import handle_message_parameters from .http import handle_message_parameters
from .invite import Invite
from .voice_client import VoiceClient
__all__ = ( __all__ = (
'TextChannel', 'TextChannel',
@ -80,7 +82,7 @@ if TYPE_CHECKING:
from .role import Role from .role import Role
from .object import Object from .object import Object
from .member import Member, VoiceState from .member import Member, VoiceState
from .abc import Snowflake, SnowflakeTime from .abc import Snowflake, SnowflakeTime, T
from .message import Message, PartialMessage, EmojiInputType from .message import Message, PartialMessage, EmojiInputType
from .mentions import AllowedMentions from .mentions import AllowedMentions
from .webhook import Webhook from .webhook import Webhook
@ -1974,8 +1976,6 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
class ForumTag(Hashable): class ForumTag(Hashable):
"""Represents a forum tag that can be applied to a thread within a :class:`ForumChannel`. """Represents a forum tag that can be applied to a thread within a :class:`ForumChannel`.
.. versionadded:: 2.0
.. container:: operations .. container:: operations
.. describe:: x == y .. describe:: x == y
@ -1994,6 +1994,7 @@ class ForumTag(Hashable):
Returns the forum tag's name. Returns the forum tag's name.
.. versionadded:: 2.0
Attributes Attributes
----------- -----------
@ -2602,11 +2603,9 @@ class ForumChannel(discord.abc.GuildChannel, Hashable):
channel_payload = { channel_payload = {
'name': name, 'name': name,
'auto_archive_duration': auto_archive_duration or self.default_auto_archive_duration, 'auto_archive_duration': auto_archive_duration or self.default_auto_archive_duration,
'location': 'Forum Channel', 'rate_limit_per_user': slowmode_delay,
'type': 11, # Private threads don't seem to be allowed 'type': 11, # Private threads don't seem to be allowed
} }
if slowmode_delay is not None:
extras['rate_limit_per_user'] = slowmode_delay
if applied_tags is not MISSING: if applied_tags is not MISSING:
channel_payload['applied_tags'] = [str(tag.id) for tag in applied_tags] channel_payload['applied_tags'] = [str(tag.id) for tag in applied_tags]
@ -2629,8 +2628,6 @@ class ForumChannel(discord.abc.GuildChannel, Hashable):
data = await state.http.start_thread_in_forum(self.id, params=params, reason=reason) data = await state.http.start_thread_in_forum(self.id, params=params, reason=reason)
thread = Thread(guild=self.guild, state=self._state, data=data) thread = Thread(guild=self.guild, state=self._state, data=data)
message = Message(state=self._state, channel=thread, data=data['message']) message = Message(state=self._state, channel=thread, data=data['message'])
if view:
self._state.store_view(view, message.id)
return ThreadWithMessage(thread=thread, message=message) return ThreadWithMessage(thread=thread, message=message)
@ -3026,9 +3023,9 @@ class DMChannel(discord.abc.Messageable, discord.abc.Connectable, discord.abc.Pr
*, *,
timeout: float = 60.0, timeout: float = 60.0,
reconnect: bool = True, reconnect: bool = True,
cls: Callable[[Client, discord.abc.Connectable], ConnectReturn] = MISSING, cls: Callable[[Client, discord.abc.Connectable], T] = VoiceClient,
ring: bool = True, ring: bool = True,
) -> ConnectReturn: ) -> T:
"""|coro| """|coro|
Connects to voice and creates a :class:`~discord.VoiceClient` to establish Connects to voice and creates a :class:`~discord.VoiceClient` to establish
@ -3558,9 +3555,9 @@ class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, discord.abc
*, *,
timeout: float = 60.0, timeout: float = 60.0,
reconnect: bool = True, reconnect: bool = True,
cls: Callable[[Client, discord.abc.Connectable], ConnectReturn] = MISSING, cls: Callable[[Client, discord.abc.Connectable], T] = VoiceClient,
ring: bool = True, ring: bool = True,
) -> ConnectReturn: ) -> T:
await self._get_channel() await self._get_channel()
call = self.call call = self.call
if call is None and ring: if call is None and ring:

12
discord/client.py

@ -3690,7 +3690,7 @@ class Client:
_state = self._connection _state = self._connection
async def _after_strategy(retrieve: int, after: Optional[Snowflake], limit: Optional[Snowflake]): async def _after_strategy(retrieve: int, after: Optional[Snowflake], limit: Optional[int]):
after_id = after.id if after else None after_id = after.id if after else None
data = await _state.http.get_payments(retrieve, after=after_id) data = await _state.http.get_payments(retrieve, after=after_id)
@ -3702,7 +3702,7 @@ class Client:
return data, after, limit return data, after, limit
async def _before_strategy(retrieve: int, before: Optional[Snowflake], limit: Optional[Snowflake]): async def _before_strategy(retrieve: int, before: Optional[Snowflake], limit: Optional[int]):
before_id = before.id if before else None before_id = before.id if before else None
data = await _state.http.get_payments(retrieve, before=before_id) data = await _state.http.get_payments(retrieve, before=before_id)
@ -4537,7 +4537,7 @@ class Client:
""" """
_state = self._connection _state = self._connection
async def strategy(retrieve: int, before: Optional[Snowflake], limit: Optional[Snowflake]): async def strategy(retrieve: int, before: Optional[Snowflake], limit: Optional[int]):
before_id = before.id if before else None before_id = before.id if before else None
data = await _state.http.get_recent_mentions( data = await _state.http.get_recent_mentions(
retrieve, before=before_id, guild_id=guild.id if guild else None, roles=roles, everyone=everyone retrieve, before=before_id, guild_id=guild.id if guild else None, roles=roles, everyone=everyone
@ -4552,14 +4552,16 @@ class Client:
return data, before, limit return data, before, limit
if isinstance(before, datetime): if isinstance(before, datetime):
before = Object(id=utils.time_snowflake(before, high=False)) state = Object(id=utils.time_snowflake(before, high=False))
else:
state = before
while True: while True:
retrieve = min(100 if limit is None else limit, 100) retrieve = min(100 if limit is None else limit, 100)
if retrieve < 1: if retrieve < 1:
return return
data, before, limit = await strategy(retrieve, before, limit) data, state, limit = await strategy(retrieve, state, limit)
# Terminate loop on next iteration; there's no data left after this # Terminate loop on next iteration; there's no data left after this
if len(data) < 100: if len(data) < 100:

48
discord/components.py

@ -72,7 +72,7 @@ class Component:
.. versionadded:: 2.0 .. versionadded:: 2.0
""" """
__slots__: Tuple[str, ...] = ('type', 'message') __slots__ = ('message',)
__repr_info__: ClassVar[Tuple[str, ...]] __repr_info__: ClassVar[Tuple[str, ...]]
message: Message message: Message
@ -119,7 +119,7 @@ class ActionRow(Component):
The originating message. The originating message.
""" """
__slots__: Tuple[str, ...] = ('children',) __slots__ = ('children',)
__repr_info__: ClassVar[Tuple[str, ...]] = __slots__ __repr_info__: ClassVar[Tuple[str, ...]] = __slots__
@ -165,7 +165,7 @@ class Button(Component):
The originating message. The originating message.
""" """
__slots__: Tuple[str, ...] = ( __slots__ = (
'style', 'style',
'custom_id', 'custom_id',
'url', 'url',
@ -189,17 +189,17 @@ class Button(Component):
except KeyError: except KeyError:
self.emoji = None self.emoji = None
@property
def type(self) -> Literal[ComponentType.button]:
""":class:`ComponentType`: The type of component."""
return ComponentType.button
def to_dict(self) -> dict: def to_dict(self) -> dict:
return { return {
'component_type': self.type.value, 'component_type': self.type.value,
'custom_id': self.custom_id, 'custom_id': self.custom_id,
} }
@property
def type(self) -> Literal[ComponentType.button]:
""":class:`ComponentType`: The type of component."""
return ComponentType.button
async def click(self) -> Union[str, Interaction]: async def click(self) -> Union[str, Interaction]:
"""|coro| """|coro|
@ -261,7 +261,7 @@ class SelectMenu(Component):
The originating message, if any. The originating message, if any.
""" """
__slots__: Tuple[str, ...] = ( __slots__ = (
'custom_id', 'custom_id',
'placeholder', 'placeholder',
'min_values', 'min_values',
@ -350,7 +350,7 @@ class SelectOption:
Whether this option is selected by default. Whether this option is selected by default.
""" """
__slots__: Tuple[str, ...] = ( __slots__ = (
'label', 'label',
'value', 'value',
'description', 'description',
@ -437,7 +437,7 @@ class TextInput(Component):
The maximum length of the text input. The maximum length of the text input.
""" """
__slots__: Tuple[str, ...] = ( __slots__ = (
'style', 'style',
'label', 'label',
'custom_id', 'custom_id',
@ -466,13 +466,6 @@ class TextInput(Component):
""":class:`ComponentType`: The type of component.""" """:class:`ComponentType`: The type of component."""
return ComponentType.text_input return ComponentType.text_input
def to_dict(self) -> dict:
return {
'type': self.type.value,
'custom_id': self.custom_id,
'value': self.value,
}
@property @property
def value(self) -> Optional[str]: def value(self) -> Optional[str]:
"""Optional[:class:`str`]: The current value of the text input. Defaults to :attr:`default`. """Optional[:class:`str`]: The current value of the text input. Defaults to :attr:`default`.
@ -514,18 +507,31 @@ class TextInput(Component):
""" """
self.value = value self.value = value
def to_dict(self) -> dict:
return {
'type': self.type.value,
'custom_id': self.custom_id,
'value': self.value,
}
@overload @overload
def _component_factory(data: ActionRowChildComponentPayload, message: Message = ...) -> Optional[ActionRowChildComponentType]: def _component_factory(
data: ActionRowChildComponentPayload, message: Message = ...
) -> Optional[ActionRowChildComponentType]:
... ...
@overload @overload
def _component_factory(data: ComponentPayload, message: Message = ...) -> Optional[Union[ActionRow, ActionRowChildComponentType]]: def _component_factory(
data: ComponentPayload, message: Message = ...
) -> Optional[Union[ActionRow, ActionRowChildComponentType]]:
... ...
def _component_factory(data: ComponentPayload, message: Message = MISSING) -> Optional[Union[ActionRow, ActionRowChildComponentType]]: def _component_factory(
data: ComponentPayload, message: Message = MISSING
) -> Optional[Union[ActionRow, ActionRowChildComponentType]]:
if data['type'] == 1: if data['type'] == 1:
return ActionRow(data, message) return ActionRow(data, message)
elif data['type'] == 2: elif data['type'] == 2:

2
discord/entitlements.py

@ -491,7 +491,7 @@ class Gift:
payment_source: Optional[:class:`PaymentSource`] payment_source: Optional[:class:`PaymentSource`]
The payment source to use for the redemption. The payment source to use for the redemption.
Only required if the gift's :attr:`flags` have :attr:`GiftFlags.payment_source_required` set to ``True``. Only required if the gift's :attr:`flags` have :attr:`GiftFlags.payment_source_required` set to ``True``.
channel: Optional[Union[:class:`TextChannel`, :class:`VoiceChannel`, :class:`Thread`, :class:`DMChannel`, :class:`GroupChannel`]] channel: Optional[Union[:class:`TextChannel`, :class:`VoiceChannel`, :class:`StageChannel`, :class:`Thread`, :class:`DMChannel`, :class:`GroupChannel`]]
The channel to redeem the gift in. This is usually the channel the gift was sent in. The channel to redeem the gift in. This is usually the channel the gift was sent in.
While this is optional, it is recommended to pass this in. While this is optional, it is recommended to pass this in.
gateway_checkout_context: Optional[:class:`str`] gateway_checkout_context: Optional[:class:`str`]

2
discord/ext/commands/converter.py

@ -1064,8 +1064,6 @@ else:
- ``Range[float, 1.0, 5.0]`` means the minimum is 1.0 and the maximum is 5.0. - ``Range[float, 1.0, 5.0]`` means the minimum is 1.0 and the maximum is 5.0.
- ``Range[str, 1, 10]`` means the minimum length is 1 and the maximum length is 10. - ``Range[str, 1, 10]`` means the minimum length is 1 and the maximum length is 10.
Inside a :class:`HybridCommand` this functions equivalently to :class:`discord.app_commands.Range`.
If the value cannot be converted to the provided type or is outside the given range, If the value cannot be converted to the provided type or is outside the given range,
:class:`~.ext.commands.BadArgument` or :class:`~.ext.commands.RangeError` is raised to :class:`~.ext.commands.BadArgument` or :class:`~.ext.commands.RangeError` is raised to
the appropriate error handlers respectively. the appropriate error handlers respectively.

19
discord/ext/commands/cooldowns.py

@ -119,7 +119,8 @@ class Cooldown:
if not current: if not current:
current = time.time() current = time.time()
tokens = self._tokens # the calculated tokens should be non-negative
tokens = max(self._tokens, 0)
if current > self._window + self.per: if current > self._window + self.per:
tokens = self.rate tokens = self.rate
@ -147,7 +148,7 @@ class Cooldown:
return 0.0 return 0.0
def update_rate_limit(self, current: Optional[float] = None) -> Optional[float]: def update_rate_limit(self, current: Optional[float] = None, *, tokens: int = 1) -> Optional[float]:
"""Updates the cooldown rate limit. """Updates the cooldown rate limit.
Parameters Parameters
@ -155,6 +156,10 @@ class Cooldown:
current: Optional[:class:`float`] current: Optional[:class:`float`]
The time in seconds since Unix epoch to update the rate limit at. The time in seconds since Unix epoch to update the rate limit at.
If not supplied, then :func:`time.time()` is used. If not supplied, then :func:`time.time()` is used.
tokens: :class:`int`
The amount of tokens to deduct from the rate limit.
.. versionadded:: 2.0
Returns Returns
------- -------
@ -170,12 +175,12 @@ class Cooldown:
if self._tokens == self.rate: if self._tokens == self.rate:
self._window = current self._window = current
# check if we are rate limited # decrement tokens by specified number
if self._tokens == 0: self._tokens -= tokens
return self.per - (current - self._window)
# we're not so decrement our tokens # check if we are rate limited and return retry-after
self._tokens -= 1 if self._tokens < 0:
return self.per - (current - self._window)
def reset(self) -> None: def reset(self) -> None:
"""Reset the cooldown to its initial state.""" """Reset the cooldown to its initial state."""

9
discord/ext/commands/core.py

@ -55,7 +55,6 @@ from .converter import Greedy, run_converters
from .cooldowns import BucketType, Cooldown, CooldownMapping, DynamicCooldownMapping, MaxConcurrency from .cooldowns import BucketType, Cooldown, CooldownMapping, DynamicCooldownMapping, MaxConcurrency
from .errors import * from .errors import *
from .parameters import Parameter, Signature from .parameters import Parameter, Signature
from discord.app_commands.commands import NUMPY_DOCSTRING_ARG_REGEX
if TYPE_CHECKING: if TYPE_CHECKING:
from typing_extensions import Concatenate, ParamSpec, Self from typing_extensions import Concatenate, ParamSpec, Self
@ -92,6 +91,14 @@ __all__ = (
MISSING: Any = discord.utils.MISSING MISSING: Any = discord.utils.MISSING
ARG_NAME_SUBREGEX = r'(?:\\?\*){0,2}(?P<name>\w+)'
ARG_DESCRIPTION_SUBREGEX = r'(?P<description>(?:.|\n)+?(?:\Z|\r?\n(?=[\S\r\n])))'
ARG_TYPE_SUBREGEX = r'(?:.+)'
NUMPY_DOCSTRING_ARG_REGEX = re.compile(
rf'^{ARG_NAME_SUBREGEX}(?:[ \t]*:)?(?:[ \t]+{ARG_TYPE_SUBREGEX})?[ \t]*\r?\n[ \t]+{ARG_DESCRIPTION_SUBREGEX}',
re.MULTILINE,
)
T = TypeVar('T') T = TypeVar('T')
CommandT = TypeVar('CommandT', bound='Command[Any, ..., Any]') CommandT = TypeVar('CommandT', bound='Command[Any, ..., Any]')
# CHT = TypeVar('CHT', bound='Check') # CHT = TypeVar('CHT', bound='Check')

18
discord/flags.py

@ -577,6 +577,15 @@ class MessageFlags(BaseFlags):
""" """
return 256 return 256
@flag_value
def link_not_discord_warning(self):
""":class:`bool`: Returns ``True`` if this message contains a link that impersonates
Discord and should show a warning.
.. versionadded:: 2.0
"""
return 1024
@flag_value @flag_value
def suppress_notifications(self): def suppress_notifications(self):
""":class:`bool`: Returns ``True`` if the message will not trigger push and desktop notifications. """:class:`bool`: Returns ``True`` if the message will not trigger push and desktop notifications.
@ -593,6 +602,14 @@ class MessageFlags(BaseFlags):
""" """
return 4096 return 4096
@flag_value
def is_voice_message(self):
""":class:`bool`: Returns ``True`` if the message's audio attachments are rendered as voice messages.
.. versionadded:: 2.0
"""
return 8192
@fill_with_flags() @fill_with_flags()
class PublicUserFlags(BaseFlags): class PublicUserFlags(BaseFlags):
@ -2223,6 +2240,7 @@ class AutoModPresets(ArrayFlags):
r"""Wraps up the Discord :class:`AutoModRule` presets. r"""Wraps up the Discord :class:`AutoModRule` presets.
.. container:: operations .. container:: operations
.. describe:: x == y .. describe:: x == y
Checks if two AutoModPresets flags are equal. Checks if two AutoModPresets flags are equal.

1
discord/gateway.py

@ -580,6 +580,7 @@ class DiscordWebSocket:
self.session_id = data['session_id'] self.session_id = data['session_id']
self.gateway = yarl.URL(data['resume_gateway_url']) self.gateway = yarl.URL(data['resume_gateway_url'])
_log.info('Connected to Gateway: %s (Session ID: %s).', ', '.join(trace), self.session_id) _log.info('Connected to Gateway: %s (Session ID: %s).', ', '.join(trace), self.session_id)
await self.voice_state() # Initial OP 4
elif event == 'RESUMED': elif event == 'RESUMED':
self._trace = trace = data.get('_trace', []) self._trace = trace = data.get('_trace', [])

30
discord/guild.py

@ -690,6 +690,8 @@ class Guild(Hashable):
"""List[:class:`ForumChannel`]: A list of forum channels that belongs to this guild. """List[:class:`ForumChannel`]: A list of forum channels that belongs to this guild.
This is sorted by the position and are in UI order from top to bottom. This is sorted by the position and are in UI order from top to bottom.
.. versionadded:: 2.0
""" """
r = [ch for ch in self._channels.values() if isinstance(ch, ForumChannel)] r = [ch for ch in self._channels.values() if isinstance(ch, ForumChannel)]
r.sort(key=lambda c: (c.position, c.id)) r.sort(key=lambda c: (c.position, c.id))
@ -2946,11 +2948,11 @@ class Guild(Hashable):
self, self,
*, *,
name: str, name: str,
start_time: datetime.datetime, start_time: datetime,
entity_type: Literal[EntityType.external] = ..., entity_type: Literal[EntityType.external] = ...,
privacy_level: PrivacyLevel = ..., privacy_level: PrivacyLevel = ...,
location: str = ..., location: str = ...,
end_time: datetime.datetime = ..., end_time: datetime = ...,
description: str = ..., description: str = ...,
image: bytes = ..., image: bytes = ...,
reason: Optional[str] = ..., reason: Optional[str] = ...,
@ -2962,11 +2964,11 @@ class Guild(Hashable):
self, self,
*, *,
name: str, name: str,
start_time: datetime.datetime, start_time: datetime,
entity_type: Literal[EntityType.stage_instance, EntityType.voice] = ..., entity_type: Literal[EntityType.stage_instance, EntityType.voice] = ...,
privacy_level: PrivacyLevel = ..., privacy_level: PrivacyLevel = ...,
channel: Snowflake = ..., channel: Snowflake = ...,
end_time: datetime.datetime = ..., end_time: datetime = ...,
description: str = ..., description: str = ...,
image: bytes = ..., image: bytes = ...,
reason: Optional[str] = ..., reason: Optional[str] = ...,
@ -2978,10 +2980,10 @@ class Guild(Hashable):
self, self,
*, *,
name: str, name: str,
start_time: datetime.datetime, start_time: datetime,
privacy_level: PrivacyLevel = ..., privacy_level: PrivacyLevel = ...,
location: str = ..., location: str = ...,
end_time: datetime.datetime = ..., end_time: datetime = ...,
description: str = ..., description: str = ...,
image: bytes = ..., image: bytes = ...,
reason: Optional[str] = ..., reason: Optional[str] = ...,
@ -2993,10 +2995,10 @@ class Guild(Hashable):
self, self,
*, *,
name: str, name: str,
start_time: datetime.datetime, start_time: datetime,
privacy_level: PrivacyLevel = ..., privacy_level: PrivacyLevel = ...,
channel: Union[VoiceChannel, StageChannel] = ..., channel: Union[VoiceChannel, StageChannel] = ...,
end_time: datetime.datetime = ..., end_time: datetime = ...,
description: str = ..., description: str = ...,
image: bytes = ..., image: bytes = ...,
reason: Optional[str] = ..., reason: Optional[str] = ...,
@ -3008,7 +3010,7 @@ class Guild(Hashable):
*, *,
name: str, name: str,
start_time: datetime, start_time: datetime,
entity_type: EntityType, entity_type: EntityType = MISSING,
privacy_level: PrivacyLevel = MISSING, privacy_level: PrivacyLevel = MISSING,
channel: Optional[Snowflake] = MISSING, channel: Optional[Snowflake] = MISSING,
location: str = MISSING, location: str = MISSING,
@ -3032,7 +3034,9 @@ class Guild(Hashable):
description: :class:`str` description: :class:`str`
The description of the scheduled event. The description of the scheduled event.
channel: Optional[:class:`abc.Snowflake`] channel: Optional[:class:`abc.Snowflake`]
The channel to send the scheduled event to. The channel to send the scheduled event to. If the channel is
a :class:`StageInstance` or :class:`VoiceChannel` then
it automatically sets the entity type.
Required if ``entity_type`` is either :attr:`EntityType.voice` or Required if ``entity_type`` is either :attr:`EntityType.voice` or
:attr:`EntityType.stage_instance`. :attr:`EntityType.stage_instance`.
@ -3047,7 +3051,11 @@ class Guild(Hashable):
privacy_level: :class:`PrivacyLevel` privacy_level: :class:`PrivacyLevel`
The privacy level of the scheduled event. The privacy level of the scheduled event.
entity_type: :class:`EntityType` entity_type: :class:`EntityType`
The entity type of the scheduled event. The entity type of the scheduled event. If the channel is a
:class:`StageInstance` or :class:`VoiceChannel` then this is
automatically set to the appropriate entity type. If no channel
is passed then the entity type is assumed to be
:attr:`EntityType.external`
image: :class:`bytes` image: :class:`bytes`
The image of the scheduled event. The image of the scheduled event.
location: :class:`str` location: :class:`str`

9
discord/http.py

@ -125,7 +125,6 @@ INTERNAL_API_VERSION = 9
_log = logging.getLogger(__name__) _log = logging.getLogger(__name__)
async def json_or_text(response: aiohttp.ClientResponse) -> Union[Dict[str, Any], str]: async def json_or_text(response: aiohttp.ClientResponse) -> Union[Dict[str, Any], str]:
text = await response.text(encoding='utf-8') text = await response.text(encoding='utf-8')
try: try:
@ -2034,8 +2033,12 @@ class HTTPClient:
def get_welcome_screen(self, guild_id: Snowflake) -> Response[welcome_screen.WelcomeScreen]: def get_welcome_screen(self, guild_id: Snowflake) -> Response[welcome_screen.WelcomeScreen]:
return self.request(Route('GET', '/guilds/{guild_id}/welcome-screen', guild_id=guild_id)) return self.request(Route('GET', '/guilds/{guild_id}/welcome-screen', guild_id=guild_id))
def edit_welcome_screen(self, guild_id: Snowflake, payload: dict, reason: Optional[str] = None) -> Response[welcome_screen.WelcomeScreen]: def edit_welcome_screen(
return self.request(Route('PATCH', '/guilds/{guild_id}/welcome-screen', guild_id=guild_id), json=payload, reason=reason) self, guild_id: Snowflake, payload: dict, reason: Optional[str] = None
) -> Response[welcome_screen.WelcomeScreen]:
return self.request(
Route('PATCH', '/guilds/{guild_id}/welcome-screen', guild_id=guild_id), json=payload, reason=reason
)
# Invite management # Invite management

2
discord/invite.py

@ -544,7 +544,7 @@ class Invite(Hashable):
channel = (guild.get_channel(channel_id) or Object(id=channel_id)) if channel_id is not None else None channel = (guild.get_channel(channel_id) or Object(id=channel_id)) if channel_id is not None else None
else: else:
guild = state._get_or_create_unavailable_guild(guild_id) if guild_id is not None else None guild = state._get_or_create_unavailable_guild(guild_id) if guild_id is not None else None
channel = Object(id=channel_id) channel = Object(id=channel_id) if channel_id is not None else None
return cls(state=state, data=data, guild=guild, channel=channel) # type: ignore return cls(state=state, data=data, guild=guild, channel=channel) # type: ignore

21
discord/message.py

@ -76,8 +76,9 @@ if TYPE_CHECKING:
from .types.message import ( from .types.message import (
Message as MessagePayload, Message as MessagePayload,
Attachment as AttachmentPayload, Attachment as AttachmentPayload,
MessageReference as MessageReferencePayload,
BaseApplication as MessageApplicationPayload, BaseApplication as MessageApplicationPayload,
Call as CallPayload,
MessageReference as MessageReferencePayload,
MessageActivity as MessageActivityPayload, MessageActivity as MessageActivityPayload,
RoleSubscriptionData as RoleSubscriptionDataPayload, RoleSubscriptionData as RoleSubscriptionDataPayload,
) )
@ -1622,7 +1623,7 @@ class Message(PartialMessage, Hashable):
if role is not None: if role is not None:
self.role_mentions.append(role) self.role_mentions.append(role)
def _handle_call(self, call) -> None: def _handle_call(self, call: Optional[CallPayload]) -> None:
if call is None or self.type is not MessageType.call: if call is None or self.type is not MessageType.call:
self.call = None self.call = None
return return
@ -1636,8 +1637,7 @@ class Message(PartialMessage, Hashable):
if user is not None: if user is not None:
participants.append(user) participants.append(user)
call['participants'] = participants self.call = CallMessage(message=self, ended_timestamp=call.get('ended_timestamp'), participants=participants)
self.call = CallMessage(message=self, **call)
def _handle_components(self, data: List[ComponentPayload]) -> None: def _handle_components(self, data: List[ComponentPayload]) -> None:
self.components = [] self.components = []
@ -1894,16 +1894,16 @@ class Message(PartialMessage, Hashable):
return 'Wondering who to invite?\nStart by inviting anyone who can help you build the server!' return 'Wondering who to invite?\nStart by inviting anyone who can help you build the server!'
if self.type is MessageType.role_subscription_purchase and self.role_subscription is not None: if self.type is MessageType.role_subscription_purchase and self.role_subscription is not None:
# TODO: figure out how the message looks like for is_renewal: true
total_months = self.role_subscription.total_months_subscribed total_months = self.role_subscription.total_months_subscribed
months = '1 month' if total_months == 1 else f'{total_months} months' months = '1 month' if total_months == 1 else f'{total_months} months'
return f'{self.author.name} joined {self.role_subscription.tier_name} and has been a subscriber of {self.guild} for {months}!' action = 'renewed' if self.role_subscription.is_renewal else 'subscribed'
return f'{self.author.name} {action} **{self.role_subscription.tier_name}** and has been a subscriber of {self.guild} for {months}!'
if self.type is MessageType.stage_start: if self.type is MessageType.stage_start:
return f'{self.author.name} started **{self.content}**.' return f'{self.author.name} started **{self.content}**'
if self.type is MessageType.stage_end: if self.type is MessageType.stage_end:
return f'{self.author.name} ended **{self.content}**.' return f'{self.author.name} ended **{self.content}**'
if self.type is MessageType.stage_speaker: if self.type is MessageType.stage_speaker:
return f'{self.author.name} is now a speaker.' return f'{self.author.name} is now a speaker.'
@ -1912,7 +1912,10 @@ class Message(PartialMessage, Hashable):
return f'{self.author.name} requested to speak.' return f'{self.author.name} requested to speak.'
if self.type is MessageType.stage_topic: if self.type is MessageType.stage_topic:
return f'{self.author.name} changed Stage topic: **{self.content}**.' return f'{self.author.name} changed the Stage topic: **{self.content}**'
if self.type is MessageType.guild_application_premium_subscription:
return f'{self.author.name} upgraded {self.application.name if self.application else "a deleted application"} to premium for this server!'
# Fallback for unknown message types # Fallback for unknown message types
return self.content return self.content

4
discord/opus.py

@ -122,7 +122,7 @@ signal_ctl: SignalCtl = {
def _err_lt(result: int, func: Callable, args: List) -> int: def _err_lt(result: int, func: Callable, args: List) -> int:
if result < OK: if result < OK:
_log.debug('error has happened in %s', func.__name__) _log.debug('Error has happened in %s.', func.__name__)
raise OpusError(result) raise OpusError(result)
return result return result
@ -130,7 +130,7 @@ def _err_lt(result: int, func: Callable, args: List) -> int:
def _err_ne(result: T, func: Callable, args: List) -> T: def _err_ne(result: T, func: Callable, args: List) -> T:
ret = args[-1]._obj ret = args[-1]._obj
if ret.value != OK: if ret.value != OK:
_log.debug('error has happened in %s', func.__name__) _log.debug('Error has happened in %s.', func.__name__)
raise OpusError(ret.value) raise OpusError(ret.value)
return result return result

16
discord/player.py

@ -196,7 +196,7 @@ class FFmpegAudio(AudioSource):
try: try:
proc.kill() proc.kill()
except Exception: except Exception:
_log.exception('Ignoring error attempting to kill ffmpeg process %s', proc.pid) _log.exception('Ignoring error attempting to kill ffmpeg process %s.', proc.pid)
if proc.poll() is None: if proc.poll() is None:
_log.info('ffmpeg process %s has not terminated. Waiting to terminate...', proc.pid) _log.info('ffmpeg process %s has not terminated. Waiting to terminate...', proc.pid)
@ -216,7 +216,7 @@ class FFmpegAudio(AudioSource):
if self._stdin is not None: if self._stdin is not None:
self._stdin.write(data) self._stdin.write(data)
except Exception: except Exception:
_log.debug('Write error for %s, this is probably not a problem', self, exc_info=True) _log.debug('Write error for %s, this is probably not a problem.', self, exc_info=True)
# at this point the source data is either exhausted or the process is fubar # at this point the source data is either exhausted or the process is fubar
self._process.terminate() self._process.terminate()
return return
@ -509,7 +509,7 @@ class FFmpegOpusAudio(FFmpegAudio):
if isinstance(method, str): if isinstance(method, str):
probefunc = getattr(cls, '_probe_codec_' + method, None) probefunc = getattr(cls, '_probe_codec_' + method, None)
if probefunc is None: if probefunc is None:
raise AttributeError(f"Invalid probe method {method!r}") raise AttributeError(f'Invalid probe method {method!r}.')
if probefunc is cls._probe_codec_native: if probefunc is cls._probe_codec_native:
fallback = cls._probe_codec_fallback fallback = cls._probe_codec_fallback
@ -526,18 +526,18 @@ class FFmpegOpusAudio(FFmpegAudio):
codec, bitrate = await loop.run_in_executor(None, lambda: probefunc(source, executable)) codec, bitrate = await loop.run_in_executor(None, lambda: probefunc(source, executable))
except Exception: except Exception:
if not fallback: if not fallback:
_log.exception("Probe '%s' using '%s' failed", method, executable) _log.exception("Probe '%s' using '%s' failed.", method, executable)
return # type: ignore return # type: ignore
_log.exception("Probe '%s' using '%s' failed, trying fallback", method, executable) _log.exception("Probe '%s' using '%s' failed, trying fallback.", method, executable)
try: try:
codec, bitrate = await loop.run_in_executor(None, lambda: fallback(source, executable)) codec, bitrate = await loop.run_in_executor(None, lambda: fallback(source, executable))
except Exception: except Exception:
_log.exception("Fallback probe using '%s' failed", executable) _log.exception("Fallback probe using '%s' failed.", executable)
else: else:
_log.debug("Fallback probe found codec=%s, bitrate=%s", codec, bitrate) _log.debug('Fallback probe found codec=%s, bitrate=%s.', codec, bitrate)
else: else:
_log.debug("Probe found codec=%s, bitrate=%s", codec, bitrate) _log.debug('Probe found codec=%s, bitrate=%s.', codec, bitrate)
finally: finally:
return codec, bitrate return codec, bitrate

5
discord/profile.py

@ -34,7 +34,6 @@ from .enums import PremiumType, try_enum
from .flags import ApplicationFlags from .flags import ApplicationFlags
from .member import Member from .member import Member
from .mixins import Hashable from .mixins import Hashable
from .object import Object
from .user import Note, User from .user import Note, User
if TYPE_CHECKING: if TYPE_CHECKING:
@ -96,9 +95,9 @@ class Profile:
state = self._state state = self._state
def get_guild(guild): def get_guild(guild):
return state._get_guild(int(guild['id'])) or Object(id=int(guild['id'])) return state._get_or_create_unavailable_guild(int(guild['id']))
return list(map(get_guild, mutual_guilds)) # type: ignore # Lying for better developer UX return list(map(get_guild, mutual_guilds))
def _parse_mutual_friends(self, mutual_friends) -> Optional[List[User]]: def _parse_mutual_friends(self, mutual_friends) -> Optional[List[User]]:
if mutual_friends is None: if mutual_friends is None:

12
discord/scheduled_event.py

@ -407,7 +407,9 @@ class ScheduledEvent(Hashable):
description: :class:`str` description: :class:`str`
The description of the scheduled event. The description of the scheduled event.
channel: Optional[:class:`~discord.abc.Snowflake`] channel: Optional[:class:`~discord.abc.Snowflake`]
The channel to put the scheduled event in. The channel to put the scheduled event in. If the channel is
a :class:`StageInstance` or :class:`VoiceChannel` then
it automatically sets the entity type.
Required if the entity type is either :attr:`EntityType.voice` or Required if the entity type is either :attr:`EntityType.voice` or
:attr:`EntityType.stage_instance`. :attr:`EntityType.stage_instance`.
@ -426,7 +428,9 @@ class ScheduledEvent(Hashable):
privacy_level: :class:`PrivacyLevel` privacy_level: :class:`PrivacyLevel`
The privacy level of the scheduled event. The privacy level of the scheduled event.
entity_type: :class:`EntityType` entity_type: :class:`EntityType`
The new entity type. The new entity type. If the channel is a :class:`StageInstance`
or :class:`VoiceChannel` then this is automatically set to the
appropriate entity type.
status: :class:`EventStatus` status: :class:`EventStatus`
The new status of the scheduled event. The new status of the scheduled event.
image: Optional[:class:`bytes`] image: Optional[:class:`bytes`]
@ -467,7 +471,7 @@ class ScheduledEvent(Hashable):
if start_time is not MISSING: if start_time is not MISSING:
if start_time.tzinfo is None: if start_time.tzinfo is None:
raise ValueError( raise ValueError(
'start_time must be an aware datetime. Consider using discord.utils.utcnow() or datetime.datetime.now().astimezone() for local time' 'start_time must be an aware datetime. Consider using discord.utils.utcnow() or datetime.datetime.now().astimezone() for local time.'
) )
payload['scheduled_start_time'] = start_time.isoformat() payload['scheduled_start_time'] = start_time.isoformat()
@ -476,7 +480,7 @@ class ScheduledEvent(Hashable):
if privacy_level is not MISSING: if privacy_level is not MISSING:
if not isinstance(privacy_level, PrivacyLevel): if not isinstance(privacy_level, PrivacyLevel):
raise TypeError('privacy_level must be of type PrivacyLevel') raise TypeError('privacy_level must be of type PrivacyLevel.')
payload['privacy_level'] = privacy_level.value payload['privacy_level'] = privacy_level.value

71
discord/settings.py

@ -28,7 +28,7 @@ import base64
from datetime import datetime, timezone from datetime import datetime, timezone
import struct import struct
import logging import logging
from typing import TYPE_CHECKING, Any, Collection, Dict, List, Optional, Sequence, Tuple, Type, Union, overload from typing import TYPE_CHECKING, Any, Collection, Dict, List, Literal, Optional, Sequence, Tuple, Type, Union, overload
from google.protobuf.json_format import MessageToDict, ParseDict from google.protobuf.json_format import MessageToDict, ParseDict
from discord_protos import PreloadedUserSettings # , FrecencyUserSettings from discord_protos import PreloadedUserSettings # , FrecencyUserSettings
@ -125,8 +125,18 @@ class _ProtoSettings:
new.settings.CopyFrom(self.settings) new.settings.CopyFrom(self.settings)
return new return new
def _get_guild(self, id: int, /) -> Union[Guild, Object]: @overload
def _get_guild(self, id: int, /, *, always_guild: Literal[True] = ...) -> Guild:
...
@overload
def _get_guild(self, id: int, /, *, always_guild: Literal[False] = ...) -> Union[Guild, Object]:
...
def _get_guild(self, id: int, /, *, always_guild: bool = False) -> Union[Guild, Object]:
id = int(id) id = int(id)
if always_guild:
return self._state._get_or_create_unavailable_guild(id)
return self._state._get_guild(id) or Object(id=id) return self._state._get_guild(id) or Object(id=id)
def to_dict(self, *, with_defaults: bool = False) -> Dict[str, Any]: def to_dict(self, *, with_defaults: bool = False) -> Dict[str, Any]:
@ -310,8 +320,8 @@ class UserSettings(_ProtoSettings):
return try_enum(SpoilerRenderOptions, self.settings.text_and_images.render_spoilers.value or 'ON_CLICK') return try_enum(SpoilerRenderOptions, self.settings.text_and_images.render_spoilers.value or 'ON_CLICK')
@property @property
def collapsed_emoji_picker_sections(self) -> Tuple[Union[EmojiPickerSection, Guild, Object], ...]: def collapsed_emoji_picker_sections(self) -> Tuple[Union[EmojiPickerSection, Guild], ...]:
"""Tuple[Union[:class:`EmojiPickerSection`, :class:`Guild`, :class:`Object`]]: A list of emoji picker sections (including guild IDs) that are collapsed.""" """Tuple[Union[:class:`EmojiPickerSection`, :class:`Guild`]]: A list of emoji picker sections (including guild IDs) that are collapsed."""
return tuple( return tuple(
self._get_guild(section) if section.isdigit() else try_enum(EmojiPickerSection, section) self._get_guild(section) if section.isdigit() else try_enum(EmojiPickerSection, section)
for section in self.settings.text_and_images.emoji_picker_collapsed_sections for section in self.settings.text_and_images.emoji_picker_collapsed_sections
@ -321,7 +331,7 @@ class UserSettings(_ProtoSettings):
def collapsed_sticker_picker_sections(self) -> Tuple[Union[StickerPickerSection, Guild, Object], ...]: def collapsed_sticker_picker_sections(self) -> Tuple[Union[StickerPickerSection, Guild, Object], ...]:
"""Tuple[Union[:class:`StickerPickerSection`, :class:`Guild`, :class:`Object`]]: A list of sticker picker sections (including guild and sticker pack IDs) that are collapsed.""" """Tuple[Union[:class:`StickerPickerSection`, :class:`Guild`, :class:`Object`]]: A list of sticker picker sections (including guild and sticker pack IDs) that are collapsed."""
return tuple( return tuple(
self._get_guild(section) if section.isdigit() else try_enum(StickerPickerSection, section) self._get_guild(section, always_guild=False) if section.isdigit() else try_enum(StickerPickerSection, section)
for section in self.settings.text_and_images.sticker_picker_collapsed_sections for section in self.settings.text_and_images.sticker_picker_collapsed_sections
) )
@ -497,8 +507,8 @@ class UserSettings(_ProtoSettings):
) )
@property @property
def restricted_guilds(self) -> List[Union[Guild, Object]]: def restricted_guilds(self) -> List[Guild]:
"""List[Union[:class:`Guild`, :class:`Object`]]: A list of guilds that you will not receive DMs from.""" """List[:class:`Guild`]: A list of guilds that you will not receive DMs from."""
return list(map(self._get_guild, self.settings.privacy.restricted_guild_ids)) return list(map(self._get_guild, self.settings.privacy.restricted_guild_ids))
@property @property
@ -545,8 +555,8 @@ class UserSettings(_ProtoSettings):
return FriendDiscoveryFlags._from_value(self.settings.privacy.friend_discovery_flags.value) return FriendDiscoveryFlags._from_value(self.settings.privacy.friend_discovery_flags.value)
@property @property
def activity_restricted_guilds(self) -> List[Union[Guild, Object]]: def activity_restricted_guilds(self) -> List[Guild]:
"""List[Union[:class:`Guild`, :class:`Object`]]: A list of guilds that your current activity will not be shown in.""" """List[:class:`Guild`]: A list of guilds that your current activity will not be shown in."""
return list(map(self._get_guild, self.settings.privacy.activity_restricted_guild_ids)) return list(map(self._get_guild, self.settings.privacy.activity_restricted_guild_ids))
@property @property
@ -555,13 +565,13 @@ class UserSettings(_ProtoSettings):
return self.settings.privacy.default_guilds_activity_restricted return self.settings.privacy.default_guilds_activity_restricted
@property @property
def activity_joining_restricted_guilds(self) -> List[Union[Guild, Object]]: def activity_joining_restricted_guilds(self) -> List[Guild]:
"""List[Union[:class:`Guild`, :class:`Object`]]: A list of guilds that will not be able to join your current activity.""" """List[:class:`Guild`]: A list of guilds that will not be able to join your current activity."""
return list(map(self._get_guild, self.settings.privacy.activity_joining_restricted_guild_ids)) return list(map(self._get_guild, self.settings.privacy.activity_joining_restricted_guild_ids))
@property @property
def message_request_restricted_guilds(self) -> List[Union[Guild, Object]]: def message_request_restricted_guilds(self) -> List[Guild]:
"""List[Union[:class:`Guild`, :class:`Object`]]: A list of guilds whose originating DMs will not be filtered into your message requests.""" """List[:class:`Guild`]: A list of guilds whose originating DMs will not be filtered into your message requests."""
return list(map(self._get_guild, self.settings.privacy.message_request_restricted_guild_ids)) return list(map(self._get_guild, self.settings.privacy.message_request_restricted_guild_ids))
@property @property
@ -683,8 +693,8 @@ class UserSettings(_ProtoSettings):
return [GuildFolder._from_settings(data=folder, state=state) for folder in self.settings.guild_folders.folders] return [GuildFolder._from_settings(data=folder, state=state) for folder in self.settings.guild_folders.folders]
@property @property
def guild_positions(self) -> List[Union[Guild, Object]]: def guild_positions(self) -> List[Guild]:
"""List[Union[:class:`Guild`, :class:`Object`]]: A list of guilds in order of the guild/guild icons that are on the left hand side of the UI.""" """List[:class:`Guild`]: A list of guilds in order of the guild/guild icons that are on the left hand side of the UI."""
return list(map(self._get_guild, self.settings.guild_folders.guild_positions)) return list(map(self._get_guild, self.settings.guild_folders.guild_positions))
# Favorites Settings # Favorites Settings
@ -1208,8 +1218,10 @@ class GuildFolder:
return self return self
def _get_guild(self, id, /) -> Union[Guild, Object]: def _get_guild(self, id, /) -> Union[Guild, Object]:
from .guild import Guild # circular import
id = int(id) id = int(id)
return self._state._get_guild(id) or Object(id=id) if self._state else Object(id=id) return self._state._get_or_create_unavailable_guild(id) if self._state else Object(id=id, type=Guild)
def to_dict(self) -> dict: def to_dict(self) -> dict:
ret = {} ret = {}
@ -1460,7 +1472,7 @@ class GuildProgress:
@property @property
def guild(self) -> Optional[Guild]: def guild(self) -> Optional[Guild]:
"""Optional[:class:`Guild`]: The guild this progress belongs to. ``None`` if state is not attached.""" """Optional[:class:`Guild`]: The guild this progress belongs to. ``None`` if state is not attached."""
return self._state._get_guild(self.guild_id) if self._state is not None else None return self._state._get_or_create_unavailable_guild(self.guild_id) if self._state is not None else None
@property @property
def hub_progress(self) -> HubProgressFlags: def hub_progress(self) -> HubProgressFlags:
@ -1682,9 +1694,8 @@ class LegacyUserSettings:
def __repr__(self) -> str: def __repr__(self) -> str:
return '<LegacyUserSettings>' return '<LegacyUserSettings>'
def _get_guild(self, id: int, /) -> Union[Guild, Object]: def _get_guild(self, id: int, /) -> Guild:
id = int(id) return self._state._get_or_create_unavailable_guild(int(id))
return self._state._get_guild(id) or Object(id=id)
def _update(self, data: Dict[str, Any]) -> None: def _update(self, data: Dict[str, Any]) -> None:
RAW_VALUES = { RAW_VALUES = {
@ -1824,16 +1835,16 @@ class LegacyUserSettings:
return await self._state.client.edit_legacy_settings(**kwargs) return await self._state.client.edit_legacy_settings(**kwargs)
@property @property
def activity_restricted_guilds(self) -> List[Union[Guild, Object]]: def activity_restricted_guilds(self) -> List[Guild]:
"""List[Union[:class:`Guild`, :class:`Object`]]: A list of guilds that your current activity will not be shown in. """List[:class:`Guild`]: A list of guilds that your current activity will not be shown in.
.. versionadded:: 2.0 .. versionadded:: 2.0
""" """
return list(map(self._get_guild, getattr(self, '_activity_restricted_guild_ids', []))) return list(map(self._get_guild, getattr(self, '_activity_restricted_guild_ids', [])))
@property @property
def activity_joining_restricted_guilds(self) -> List[Union[Guild, Object]]: def activity_joining_restricted_guilds(self) -> List[Guild]:
"""List[Union[:class:`Guild`, :class:`Object`]]: A list of guilds that will not be able to join your current activity. """List[:class:`Guild`]: A list of guilds that will not be able to join your current activity.
.. versionadded:: 2.0 .. versionadded:: 2.0
""" """
@ -1873,8 +1884,8 @@ class LegacyUserSettings:
] ]
@property @property
def guild_positions(self) -> List[Union[Guild, Object]]: def guild_positions(self) -> List[Guild]:
"""List[Union[:class:`Guild`, :class:`Object`]]: A list of guilds in order of the guild/guild icons that are on the left hand side of the UI.""" """List[:class:`Guild`]: A list of guilds in order of the guild/guild icons that are on the left hand side of the UI."""
return list(map(self._get_guild, getattr(self, '_guild_positions', []))) return list(map(self._get_guild, getattr(self, '_guild_positions', [])))
@property @property
@ -1893,8 +1904,8 @@ class LegacyUserSettings:
return getattr(self, '_passwordless', False) return getattr(self, '_passwordless', False)
@property @property
def restricted_guilds(self) -> List[Union[Guild, Object]]: def restricted_guilds(self) -> List[Guild]:
"""List[Union[:class:`Guild`, :class:`Object`]]: A list of guilds that you will not receive DMs from.""" """List[:class:`Guild`]: A list of guilds that you will not receive DMs from."""
return list(map(self._get_guild, getattr(self, '_restricted_guilds', []))) return list(map(self._get_guild, getattr(self, '_restricted_guilds', [])))
@property @property
@ -2007,7 +2018,7 @@ class ChannelSettings:
@property @property
def channel(self) -> Union[GuildChannel, PrivateChannel]: def channel(self) -> Union[GuildChannel, PrivateChannel]:
"""Union[:class:`abc.GuildChannel`, :class:`abc.PrivateChannel`]: Returns the channel these settings are for.""" """Union[:class:`abc.GuildChannel`, :class:`abc.PrivateChannel`]: Returns the channel these settings are for."""
guild = self._state._get_guild(self._guild_id) guild = self._state._get_or_create_unavailable_guild(self._guild_id) if self._guild_id else None
if guild: if guild:
channel = guild.get_channel(self._channel_id) channel = guild.get_channel(self._channel_id)
else: else:
@ -2165,7 +2176,7 @@ class GuildSettings:
If the returned value is a :class:`ClientUser` then the settings are for the user's private channels. If the returned value is a :class:`ClientUser` then the settings are for the user's private channels.
""" """
if self._guild_id: if self._guild_id:
return self._state._get_guild(self._guild_id) or Object(id=self._guild_id) # type: ignore # Lying for better developer UX return self._state._get_or_create_unavailable_guild(self._guild_id)
return self._state.user # type: ignore # Should always be present here return self._state.user # type: ignore # Should always be present here
@property @property

2
discord/state.py

@ -2631,7 +2631,7 @@ class ConnectionState:
# parse_guild_application_commands_update = parse_nothing # Grabbed directly in command iterators # parse_guild_application_commands_update = parse_nothing # Grabbed directly in command iterators
def _get_reaction_user(self, channel: MessageableChannel, user_id: int) -> Optional[Union[User, Member]]: def _get_reaction_user(self, channel: MessageableChannel, user_id: int) -> Optional[Union[User, Member]]:
if isinstance(channel, (TextChannel, Thread, VoiceChannel)): if isinstance(channel, (TextChannel, Thread, VoiceChannel, StageChannel)):
return channel.guild.get_member(user_id) return channel.guild.get_member(user_id)
return self.get_user(user_id) return self.get_user(user_id)

2
discord/team.py

@ -412,7 +412,7 @@ class Team(Hashable):
The payout received. The payout received.
""" """
async def strategy(retrieve: int, before: Optional[Snowflake], limit: Optional[Snowflake]): async def strategy(retrieve: int, before: Optional[Snowflake], limit: Optional[int]):
before_id = before.id if before else None before_id = before.id if before else None
data = await self._state.http.get_team_payouts(self.id, limit=retrieve, before=before_id) data = await self._state.http.get_team_payouts(self.id, limit=retrieve, before=before_id)

20
discord/threads.py

@ -261,10 +261,7 @@ class Thread(Messageable, Hashable):
@property @property
def jump_url(self) -> str: def jump_url(self) -> str:
""":class:`str`: Returns a URL that allows the client to jump to the thread. """:class:`str`: Returns a URL that allows the client to jump to the thread."""
.. versionadded:: 2.0
"""
return f'https://discord.com/channels/{self.guild.id}/{self.id}' return f'https://discord.com/channels/{self.guild.id}/{self.id}'
@property @property
@ -277,17 +274,14 @@ class Thread(Messageable, Hashable):
@property @property
def applied_tags(self) -> List[ForumTag]: def applied_tags(self) -> List[ForumTag]:
"""List[:class:`ForumTag`]: A list of tags applied to this thread. """List[:class:`ForumTag`]: A list of tags applied to this thread."""
.. versionadded:: 2.0
"""
tags = [] tags = []
if self.parent is None or self.parent.type != ChannelType.forum: if self.parent is None or self.parent.type != ChannelType.forum:
return tags return tags
parent = self.parent parent = self.parent
for tag_id in self._applied_tags: for tag_id in self._applied_tags:
tag = parent.get_tag(tag_id) tag = parent.get_tag(tag_id) # type: ignore
if tag is not None: if tag is not None:
tags.append(tag) tags.append(tag)
@ -607,8 +601,6 @@ class Thread(Messageable, Hashable):
A value of ``0`` disables slowmode. The maximum value possible is ``21600``. A value of ``0`` disables slowmode. The maximum value possible is ``21600``.
applied_tags: Sequence[:class:`ForumTag`] applied_tags: Sequence[:class:`ForumTag`]
The new tags to apply to the thread. There can only be up to 5 tags applied to a thread. The new tags to apply to the thread. There can only be up to 5 tags applied to a thread.
.. versionadded:: 2.0
reason: Optional[:class:`str`] reason: Optional[:class:`str`]
The reason for editing this thread. Shows up on the audit log. The reason for editing this thread. Shows up on the audit log.
@ -663,8 +655,6 @@ class Thread(Messageable, Hashable):
The parent channel must be a :class:`ForumChannel`. The parent channel must be a :class:`ForumChannel`.
.. versionadded:: 2.0
Parameters Parameters
----------- -----------
\*tags: :class:`abc.Snowflake` \*tags: :class:`abc.Snowflake`
@ -696,8 +686,6 @@ class Thread(Messageable, Hashable):
The parent channel must be a :class:`ForumChannel`. The parent channel must be a :class:`ForumChannel`.
.. versionadded:: 2.0
Parameters Parameters
----------- -----------
\*tags: :class:`abc.Snowflake` \*tags: :class:`abc.Snowflake`
@ -847,8 +835,6 @@ class Thread(Messageable, Hashable):
This is useful if you want to work with a message and only have its ID without This is useful if you want to work with a message and only have its ID without
doing an unnecessary API call. doing an unnecessary API call.
.. versionadded:: 2.0
Parameters Parameters
------------ ------------
message_id: :class:`int` message_id: :class:`int`

6
discord/types/message.py

@ -86,6 +86,11 @@ class MessageReference(TypedDict, total=False):
fail_if_not_exists: bool fail_if_not_exists: bool
class Call(TypedDict):
participants: List[Snowflake]
ended_timestamp: Optional[str]
class RoleSubscriptionData(TypedDict): class RoleSubscriptionData(TypedDict):
role_subscription_listing_id: Snowflake role_subscription_listing_id: Snowflake
tier_name: str tier_name: str
@ -127,6 +132,7 @@ class Message(PartialMessage):
interaction: NotRequired[MessageInteraction] interaction: NotRequired[MessageInteraction]
components: NotRequired[List[Component]] components: NotRequired[List[Component]]
position: NotRequired[int] position: NotRequired[int]
call: NotRequired[Call]
role_subscription_data: NotRequired[RoleSubscriptionData] role_subscription_data: NotRequired[RoleSubscriptionData]

18
discord/webhook/async_.py

@ -43,7 +43,7 @@ from ..user import BaseUser, User
from ..flags import MessageFlags from ..flags import MessageFlags
from ..asset import Asset from ..asset import Asset
from ..partial_emoji import PartialEmoji from ..partial_emoji import PartialEmoji
from ..http import Route, handle_message_parameters, HTTPClient from ..http import Route, handle_message_parameters, json_or_text, HTTPClient
from ..mixins import Hashable from ..mixins import Hashable
from ..channel import TextChannel, ForumChannel, PartialMessageable from ..channel import TextChannel, ForumChannel, PartialMessageable
from ..file import File from ..file import File
@ -800,7 +800,7 @@ class BaseWebhook(Hashable):
@property @property
def channel(self) -> Optional[Union[ForumChannel, VoiceChannel, TextChannel]]: def channel(self) -> Optional[Union[ForumChannel, VoiceChannel, TextChannel]]:
"""Optional[Union[:class:`ForumChannel`, :class:`VoiceChannel`, :class:`TextChannel`]]: The channel this webhook belongs to. """Optional[Union[:class:`ForumChannel`, :class:`VoiceChannel`, :class:`StageChannel`, :class:`TextChannel`]]: The channel this webhook belongs to.
If this is a partial webhook, then this will always return ``None``. If this is a partial webhook, then this will always return ``None``.
""" """
@ -957,7 +957,7 @@ class Webhook(BaseWebhook):
*, *,
session: aiohttp.ClientSession = MISSING, session: aiohttp.ClientSession = MISSING,
client: Client = MISSING, client: Client = MISSING,
bot_token: Optional[str] = None, user_token: Optional[str] = None,
) -> Self: ) -> Self:
"""Creates a partial :class:`Webhook`. """Creates a partial :class:`Webhook`.
@ -979,8 +979,8 @@ class Webhook(BaseWebhook):
while this is given then the client's internal session will be used. while this is given then the client's internal session will be used.
.. versionadded:: 2.0 .. versionadded:: 2.0
bot_token: Optional[:class:`str`] user_token: Optional[:class:`str`]
The bot authentication token for authenticated requests The user authentication token for authenticated requests
involving the webhook. involving the webhook.
.. versionadded:: 2.0 .. versionadded:: 2.0
@ -1011,7 +1011,7 @@ class Webhook(BaseWebhook):
if session is MISSING: if session is MISSING:
raise TypeError('session or client must be given') raise TypeError('session or client must be given')
return cls(data, session, token=bot_token, state=state) return cls(data, session, token=user_token, state=state)
@classmethod @classmethod
def from_url( def from_url(
@ -1020,7 +1020,7 @@ class Webhook(BaseWebhook):
*, *,
session: aiohttp.ClientSession = MISSING, session: aiohttp.ClientSession = MISSING,
client: Client = MISSING, client: Client = MISSING,
bot_token: Optional[str] = None, user_token: Optional[str] = None,
) -> Self: ) -> Self:
"""Creates a partial :class:`Webhook` from a webhook URL. """Creates a partial :class:`Webhook` from a webhook URL.
@ -1044,7 +1044,7 @@ class Webhook(BaseWebhook):
while this is given then the client's internal session will be used. while this is given then the client's internal session will be used.
.. versionadded:: 2.0 .. versionadded:: 2.0
bot_token: Optional[:class:`str`] user_token: Optional[:class:`str`]
The bot authentication token for authenticated requests The bot authentication token for authenticated requests
involving the webhook. involving the webhook.
@ -1078,7 +1078,7 @@ class Webhook(BaseWebhook):
data: Dict[str, Any] = m.groupdict() data: Dict[str, Any] = m.groupdict()
data['type'] = 1 data['type'] = 1
return cls(data, session, token=bot_token, state=state) # type: ignore # Casting dict[str, Any] to WebhookPayload return cls(data, session, token=user_token, state=state) # type: ignore # Casting dict[str, Any] to WebhookPayload
@classmethod @classmethod
def _as_follower(cls, data, *, channel, user) -> Self: def _as_follower(cls, data, *, channel, user) -> Self:

12
discord/webhook/sync.py

@ -615,7 +615,7 @@ class SyncWebhook(BaseWebhook):
return f'https://discord.com/api/webhooks/{self.id}/{self.token}' return f'https://discord.com/api/webhooks/{self.id}/{self.token}'
@classmethod @classmethod
def partial(cls, id: int, token: str, *, session: Session = MISSING, bot_token: Optional[str] = None) -> SyncWebhook: def partial(cls, id: int, token: str, *, session: Session = MISSING, user_token: Optional[str] = None) -> SyncWebhook:
"""Creates a partial :class:`Webhook`. """Creates a partial :class:`Webhook`.
Parameters Parameters
@ -629,7 +629,7 @@ class SyncWebhook(BaseWebhook):
that the library does not manage the session and that the library does not manage the session and
will not close it. If not given, the ``requests`` will not close it. If not given, the ``requests``
auto session creation functions are used instead. auto session creation functions are used instead.
bot_token: Optional[:class:`str`] user_token: Optional[:class:`str`]
The bot authentication token for authenticated requests The bot authentication token for authenticated requests
involving the webhook. involving the webhook.
@ -651,10 +651,10 @@ class SyncWebhook(BaseWebhook):
raise TypeError(f'expected requests.Session not {session.__class__!r}') raise TypeError(f'expected requests.Session not {session.__class__!r}')
else: else:
session = requests # type: ignore session = requests # type: ignore
return cls(data, session, token=bot_token) return cls(data, session, token=user_token)
@classmethod @classmethod
def from_url(cls, url: str, *, session: Session = MISSING, bot_token: Optional[str] = None) -> SyncWebhook: def from_url(cls, url: str, *, session: Session = MISSING, user_token: Optional[str] = None) -> SyncWebhook:
"""Creates a partial :class:`Webhook` from a webhook URL. """Creates a partial :class:`Webhook` from a webhook URL.
Parameters Parameters
@ -666,7 +666,7 @@ class SyncWebhook(BaseWebhook):
that the library does not manage the session and that the library does not manage the session and
will not close it. If not given, the ``requests`` will not close it. If not given, the ``requests``
auto session creation functions are used instead. auto session creation functions are used instead.
bot_token: Optional[:class:`str`] user_token: Optional[:class:`str`]
The bot authentication token for authenticated requests The bot authentication token for authenticated requests
involving the webhook. involving the webhook.
@ -694,7 +694,7 @@ class SyncWebhook(BaseWebhook):
raise TypeError(f'expected requests.Session not {session.__class__!r}') raise TypeError(f'expected requests.Session not {session.__class__!r}')
else: else:
session = requests # type: ignore session = requests # type: ignore
return cls(data, session, token=bot_token) # type: ignore return cls(data, session, token=user_token) # type: ignore
def fetch(self, *, prefer_auth: bool = True) -> SyncWebhook: def fetch(self, *, prefer_auth: bool = True) -> SyncWebhook:
"""Fetches the current webhook. """Fetches the current webhook.

8
discord/welcome_screen.py

@ -62,7 +62,9 @@ class WelcomeChannel:
The emoji shown under the description. The emoji shown under the description.
""" """
def __init__(self, *, channel: Snowflake, description: str, emoji: Optional[Union[PartialEmoji, Emoji, str]] = None) -> None: def __init__(
self, *, channel: Snowflake, description: str, emoji: Optional[Union[PartialEmoji, Emoji, str]] = None
) -> None:
self.channel = channel self.channel = channel
self.description = description self.description = description
@ -129,7 +131,9 @@ class WelcomeScreen:
state = self.guild._state state = self.guild._state
channels = data.get('welcome_channels', []) channels = data.get('welcome_channels', [])
self.welcome_channels: List[WelcomeChannel] = [WelcomeChannel._from_dict(data=channel, state=state) for channel in channels] self.welcome_channels: List[WelcomeChannel] = [
WelcomeChannel._from_dict(data=channel, state=state) for channel in channels
]
self.description: str = data.get('description', '') self.description: str = data.get('description', '')
def __repr__(self) -> str: def __repr__(self) -> str:

29
docs/api.rst

@ -1152,8 +1152,7 @@ Reactions
.. note:: .. note::
Consider using :func:`on_raw_reaction_remove` if you need this and do not want Consider using :func:`on_raw_reaction_remove` if you need this and do not have a complete member cache.
to enable the members intent.
:param reaction: The current state of the reaction. :param reaction: The current state of the reaction.
:type reaction: :class:`Reaction` :type reaction: :class:`Reaction`
@ -5722,9 +5721,9 @@ AuditLogDiff
.. attribute:: bitrate .. attribute:: bitrate
The bitrate of a :class:`VoiceChannel`. The bitrate of a :class:`VoiceChannel` or :class:`StageChannel`.
See also :attr:`VoiceChannel.bitrate`. See also :attr:`VoiceChannel.bitrate`, :attr:`StageChannel.bitrate`.
:type: :class:`int` :type: :class:`int`
@ -6055,12 +6054,6 @@ AuditLogDiff
:type: :class:`Asset` :type: :class:`Asset`
.. attribute:: app_command_permissions
The permissions of the app command.
:type: :class:`~discord.app_commands.AppCommandPermissions`
.. attribute:: enabled .. attribute:: enabled
Whether the automod rule is active or not. Whether the automod rule is active or not.
@ -7322,22 +7315,6 @@ RawEvent
.. autoclass:: RawThreadDeleteEvent() .. autoclass:: RawThreadDeleteEvent()
:members: :members:
PartialWebhookGuild
~~~~~~~~~~~~~~~~~~~~
.. attributetable:: PartialWebhookGuild
.. autoclass:: PartialWebhookGuild()
:members:
PartialWebhookChannel
~~~~~~~~~~~~~~~~~~~~~~~
.. attributetable:: PartialWebhookChannel
.. autoclass:: PartialWebhookChannel()
:members:
.. _discord_api_data: .. _discord_api_data:
Data Classes Data Classes

2
docs/faq.rst

@ -341,7 +341,7 @@ Quick example:
Is there an event for audit log entries being created? Is there an event for audit log entries being created?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This event is now available in the library and Discord as of version 2.2. It can be found under :func:`on_audit_log_entry_create`. This event is now available in the library and Discord as of version 2.0. It can be found under :func:`on_audit_log_entry_create`.
Commands Extension Commands Extension

4
docs/migrating.rst

@ -887,9 +887,7 @@ Text in Voice
In order to support text in voice functionality, a few changes had to be made: In order to support text in voice functionality, a few changes had to be made:
- :class:`VoiceChannel` is now :class:`abc.Messageable` so it can have messages sent and received. - :class:`VoiceChannel` is now :class:`abc.Messageable` so it can have messages sent and received.
- :attr:`Message.channel` can now be :class:`VoiceChannel`. - :attr:`Message.channel` can now be :class:`VoiceChannel` and :class:`StageChannel`.
In the future this may include :class:`StageChannel` when Discord implements it.
Removal of ``StoreChannel`` Removal of ``StoreChannel``
----------------------------- -----------------------------

12
setup.py

@ -55,7 +55,7 @@ extras_require = {
'pytest-cov', 'pytest-cov',
'pytest-mock', 'pytest-mock',
'typing-extensions>=4.3,<5', 'typing-extensions>=4.3,<5',
] ],
} }
setup( setup(
@ -63,17 +63,17 @@ setup(
author='Dolfies', author='Dolfies',
url='https://github.com/dolfies/discord.py-self', url='https://github.com/dolfies/discord.py-self',
project_urls={ project_urls={
"Documentation": "https://discordpy-self.readthedocs.io/en/latest/", 'Documentation': 'https://discordpy-self.readthedocs.io/en/latest/',
"Issue tracker": "https://github.com/dolfies/discord.py-self/issues", 'Issue tracker': 'https://github.com/dolfies/discord.py-self/issues',
"Project updates": "https://t.me/dpy_self", 'Project updates': 'https://t.me/dpy_self',
"Discussion & support": "https://t.me/dpy_self_discussions", 'Discussion & support': 'https://t.me/dpy_self_discussions',
}, },
version=version, version=version,
packages=find_packages() + [f'{prefix}.ext.commands', f'{prefix}.ext.tasks'], packages=find_packages() + [f'{prefix}.ext.commands', f'{prefix}.ext.tasks'],
license='MIT', license='MIT',
description='A Python wrapper for the Discord user API', description='A Python wrapper for the Discord user API',
long_description=readme, long_description=readme,
long_description_content_type="text/x-rst", long_description_content_type='text/x-rst',
include_package_data=True, include_package_data=True,
install_requires=requirements, install_requires=requirements,
extras_require=extras_require, extras_require=extras_require,

Loading…
Cancel
Save