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:
version: '1.1.289'
warnings: false
no-comments: ${{ matrix.python-version != '3.x' }}
no-comments: false
- name: Run black
if: ${{ always() && steps.install-deps.outcome == 'success' }}

1
discord/abc.py

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

34
discord/activity.py

@ -836,42 +836,42 @@ class CustomActivity(BaseActivity):
return ActivityType.custom
def to_dict(self) -> ActivityPayload:
o = {
payload = {
'type': ActivityType.custom.value,
'state': self.name,
'name': 'Custom Status', # Not a confusing API at all
}
if self.emoji:
o['emoji'] = self.emoji.to_dict()
return o # type: ignore
payload['emoji'] = self.emoji.to_dict()
return payload # type: ignore
def to_legacy_settings_dict(self) -> Dict[str, Any]:
o: Dict[str, Optional[Union[str, int]]] = {}
def to_legacy_settings_dict(self) -> Dict[str, Optional[Union[str, int]]]:
payload: Dict[str, Optional[Union[str, int]]] = {}
if self.name:
o['text'] = self.name
payload['text'] = self.name
if self.emoji:
emoji = self.emoji
o['emoji_name'] = emoji.name
payload['emoji_name'] = emoji.name
if emoji.id:
o['emoji_id'] = emoji.id
payload['emoji_id'] = emoji.id
if self.expires_at is not None:
o['expires_at'] = self.expires_at.isoformat()
return o
payload['expires_at'] = self.expires_at.isoformat()
return payload
def to_settings_dict(self) -> Dict[str, Any]:
o: Dict[str, Optional[Union[str, int]]] = {}
def to_settings_dict(self) -> Dict[str, Optional[Union[str, int]]]:
payload: Dict[str, Optional[Union[str, int]]] = {}
if self.name:
o['text'] = self.name
payload['text'] = self.name
if self.emoji:
emoji = self.emoji
o['emoji_name'] = emoji.name
payload['emoji_name'] = emoji.name
if emoji.id:
o['emoji_id'] = emoji.id
payload['emoji_id'] = emoji.id
if self.expires_at is not None:
o['expires_at_ms'] = int(self.expires_at.timestamp() * 1000)
return o
payload['expires_at_ms'] = int(self.expires_at.timestamp() * 1000)
return payload
def __eq__(self, other: object) -> bool:
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`
The ID of the application to be authorized.
scopes: List[:class:`str`]
The list of `OAuth2 scopes <https://discord.com/developers/docs/topics/oauth2#shared-resources-oauth2-scopes>`_
to add the application with.
The list of :ddocs:`OAuth2 scopes <topics/oauth2#shared-resources-oauth2-scopes>` to add the application with.
permissions: :class:`Permissions`
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.
verify_key: :class:`str`
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`]
The application's terms of service URL, if set.
privacy_policy_url: Optional[:class:`str`]
@ -2935,7 +2934,7 @@ class Application(PartialApplication):
_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
data = await _state.http.get_app_entitlements(
self.id,
@ -2956,7 +2955,7 @@ class Application(PartialApplication):
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
data = await _state.http.get_app_entitlements(
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.
content: :class:`str`
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`]
The matched keyword from the triggering message.
matched_content: Optional[:class:`str`]
The matched content from the triggering message.
Requires the :attr:`Intents.message_content` or it will always return ``None``.
"""
__slots__ = (

13
discord/calls.py

@ -76,7 +76,7 @@ class CallMessage:
__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.ended_timestamp = utils.parse_time(ended_timestamp)
self.participants = participants
@ -188,8 +188,8 @@ class PrivateCall:
return list(self._ringing)
@property
def initiator(self) -> User:
""":class:`.abc.User`: Returns the user that started the call."""
def initiator(self) -> Optional[User]:
"""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)
@property
@ -211,10 +211,9 @@ class PrivateCall:
}
@cached_slot_property('_cs_message')
def message(self) -> Message:
""":class:`Message`: The message associated with this call."""
# 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) # type: ignore
def message(self) -> Optional[Message]:
"""Optional[:class:`Message`]: The message associated with this call. Sometimes may not be cached."""
return self._state._get_message(self._message_id)
async def fetch_message(self) -> Message:
"""|coro|

21
discord/channel.py

@ -59,6 +59,8 @@ from .threads import Thread
from .partial_emoji import _EmojiTag, PartialEmoji
from .flags import ChannelFlags
from .http import handle_message_parameters
from .invite import Invite
from .voice_client import VoiceClient
__all__ = (
'TextChannel',
@ -80,7 +82,7 @@ if TYPE_CHECKING:
from .role import Role
from .object import Object
from .member import Member, VoiceState
from .abc import Snowflake, SnowflakeTime
from .abc import Snowflake, SnowflakeTime, T
from .message import Message, PartialMessage, EmojiInputType
from .mentions import AllowedMentions
from .webhook import Webhook
@ -1974,8 +1976,6 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
class ForumTag(Hashable):
"""Represents a forum tag that can be applied to a thread within a :class:`ForumChannel`.
.. versionadded:: 2.0
.. container:: operations
.. describe:: x == y
@ -1994,6 +1994,7 @@ class ForumTag(Hashable):
Returns the forum tag's name.
.. versionadded:: 2.0
Attributes
-----------
@ -2602,11 +2603,9 @@ class ForumChannel(discord.abc.GuildChannel, Hashable):
channel_payload = {
'name': name,
'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
}
if slowmode_delay is not None:
extras['rate_limit_per_user'] = slowmode_delay
if applied_tags is not MISSING:
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)
thread = Thread(guild=self.guild, state=self._state, data=data)
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)
@ -3026,9 +3023,9 @@ class DMChannel(discord.abc.Messageable, discord.abc.Connectable, discord.abc.Pr
*,
timeout: float = 60.0,
reconnect: bool = True,
cls: Callable[[Client, discord.abc.Connectable], ConnectReturn] = MISSING,
cls: Callable[[Client, discord.abc.Connectable], T] = VoiceClient,
ring: bool = True,
) -> ConnectReturn:
) -> T:
"""|coro|
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,
reconnect: bool = True,
cls: Callable[[Client, discord.abc.Connectable], ConnectReturn] = MISSING,
cls: Callable[[Client, discord.abc.Connectable], T] = VoiceClient,
ring: bool = True,
) -> ConnectReturn:
) -> T:
await self._get_channel()
call = self.call
if call is None and ring:

12
discord/client.py

@ -3690,7 +3690,7 @@ class Client:
_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
data = await _state.http.get_payments(retrieve, after=after_id)
@ -3702,7 +3702,7 @@ class Client:
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
data = await _state.http.get_payments(retrieve, before=before_id)
@ -4537,7 +4537,7 @@ class Client:
"""
_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
data = await _state.http.get_recent_mentions(
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
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:
retrieve = min(100 if limit is None else limit, 100)
if retrieve < 1:
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
if len(data) < 100:

48
discord/components.py

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

2
discord/entitlements.py

@ -491,7 +491,7 @@ class Gift:
payment_source: Optional[:class:`PaymentSource`]
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``.
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.
While this is optional, it is recommended to pass this in.
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[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,
:class:`~.ext.commands.BadArgument` or :class:`~.ext.commands.RangeError` is raised to
the appropriate error handlers respectively.

19
discord/ext/commands/cooldowns.py

@ -119,7 +119,8 @@ class Cooldown:
if not current:
current = time.time()
tokens = self._tokens
# the calculated tokens should be non-negative
tokens = max(self._tokens, 0)
if current > self._window + self.per:
tokens = self.rate
@ -147,7 +148,7 @@ class Cooldown:
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.
Parameters
@ -155,6 +156,10 @@ class Cooldown:
current: Optional[:class:`float`]
The time in seconds since Unix epoch to update the rate limit at.
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
-------
@ -170,12 +175,12 @@ class Cooldown:
if self._tokens == self.rate:
self._window = current
# check if we are rate limited
if self._tokens == 0:
return self.per - (current - self._window)
# decrement tokens by specified number
self._tokens -= tokens
# we're not so decrement our tokens
self._tokens -= 1
# check if we are rate limited and return retry-after
if self._tokens < 0:
return self.per - (current - self._window)
def reset(self) -> None:
"""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 .errors import *
from .parameters import Parameter, Signature
from discord.app_commands.commands import NUMPY_DOCSTRING_ARG_REGEX
if TYPE_CHECKING:
from typing_extensions import Concatenate, ParamSpec, Self
@ -92,6 +91,14 @@ __all__ = (
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')
CommandT = TypeVar('CommandT', bound='Command[Any, ..., Any]')
# CHT = TypeVar('CHT', bound='Check')

18
discord/flags.py

@ -577,6 +577,15 @@ class MessageFlags(BaseFlags):
"""
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
def suppress_notifications(self):
""":class:`bool`: Returns ``True`` if the message will not trigger push and desktop notifications.
@ -593,6 +602,14 @@ class MessageFlags(BaseFlags):
"""
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()
class PublicUserFlags(BaseFlags):
@ -2223,6 +2240,7 @@ class AutoModPresets(ArrayFlags):
r"""Wraps up the Discord :class:`AutoModRule` presets.
.. container:: operations
.. describe:: x == y
Checks if two AutoModPresets flags are equal.

1
discord/gateway.py

@ -580,6 +580,7 @@ class DiscordWebSocket:
self.session_id = data['session_id']
self.gateway = yarl.URL(data['resume_gateway_url'])
_log.info('Connected to Gateway: %s (Session ID: %s).', ', '.join(trace), self.session_id)
await self.voice_state() # Initial OP 4
elif event == 'RESUMED':
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.
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.sort(key=lambda c: (c.position, c.id))
@ -2946,11 +2948,11 @@ class Guild(Hashable):
self,
*,
name: str,
start_time: datetime.datetime,
start_time: datetime,
entity_type: Literal[EntityType.external] = ...,
privacy_level: PrivacyLevel = ...,
location: str = ...,
end_time: datetime.datetime = ...,
end_time: datetime = ...,
description: str = ...,
image: bytes = ...,
reason: Optional[str] = ...,
@ -2962,11 +2964,11 @@ class Guild(Hashable):
self,
*,
name: str,
start_time: datetime.datetime,
start_time: datetime,
entity_type: Literal[EntityType.stage_instance, EntityType.voice] = ...,
privacy_level: PrivacyLevel = ...,
channel: Snowflake = ...,
end_time: datetime.datetime = ...,
end_time: datetime = ...,
description: str = ...,
image: bytes = ...,
reason: Optional[str] = ...,
@ -2978,10 +2980,10 @@ class Guild(Hashable):
self,
*,
name: str,
start_time: datetime.datetime,
start_time: datetime,
privacy_level: PrivacyLevel = ...,
location: str = ...,
end_time: datetime.datetime = ...,
end_time: datetime = ...,
description: str = ...,
image: bytes = ...,
reason: Optional[str] = ...,
@ -2993,10 +2995,10 @@ class Guild(Hashable):
self,
*,
name: str,
start_time: datetime.datetime,
start_time: datetime,
privacy_level: PrivacyLevel = ...,
channel: Union[VoiceChannel, StageChannel] = ...,
end_time: datetime.datetime = ...,
end_time: datetime = ...,
description: str = ...,
image: bytes = ...,
reason: Optional[str] = ...,
@ -3008,7 +3010,7 @@ class Guild(Hashable):
*,
name: str,
start_time: datetime,
entity_type: EntityType,
entity_type: EntityType = MISSING,
privacy_level: PrivacyLevel = MISSING,
channel: Optional[Snowflake] = MISSING,
location: str = MISSING,
@ -3032,7 +3034,9 @@ class Guild(Hashable):
description: :class:`str`
The description of the scheduled event.
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
:attr:`EntityType.stage_instance`.
@ -3047,7 +3051,11 @@ class Guild(Hashable):
privacy_level: :class:`PrivacyLevel`
The privacy level of the scheduled event.
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`
The image of the scheduled event.
location: :class:`str`

9
discord/http.py

@ -125,7 +125,6 @@ INTERNAL_API_VERSION = 9
_log = logging.getLogger(__name__)
async def json_or_text(response: aiohttp.ClientResponse) -> Union[Dict[str, Any], str]:
text = await response.text(encoding='utf-8')
try:
@ -2034,8 +2033,12 @@ class HTTPClient:
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))
def edit_welcome_screen(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)
def edit_welcome_screen(
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

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
else:
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

21
discord/message.py

@ -76,8 +76,9 @@ if TYPE_CHECKING:
from .types.message import (
Message as MessagePayload,
Attachment as AttachmentPayload,
MessageReference as MessageReferencePayload,
BaseApplication as MessageApplicationPayload,
Call as CallPayload,
MessageReference as MessageReferencePayload,
MessageActivity as MessageActivityPayload,
RoleSubscriptionData as RoleSubscriptionDataPayload,
)
@ -1622,7 +1623,7 @@ class Message(PartialMessage, Hashable):
if role is not None:
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:
self.call = None
return
@ -1636,8 +1637,7 @@ class Message(PartialMessage, Hashable):
if user is not None:
participants.append(user)
call['participants'] = participants
self.call = CallMessage(message=self, **call)
self.call = CallMessage(message=self, ended_timestamp=call.get('ended_timestamp'), participants=participants)
def _handle_components(self, data: List[ComponentPayload]) -> None:
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!'
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
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:
return f'{self.author.name} started **{self.content}**.'
return f'{self.author.name} started **{self.content}**'
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:
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.'
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
return self.content

4
discord/opus.py

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

16
discord/player.py

@ -196,7 +196,7 @@ class FFmpegAudio(AudioSource):
try:
proc.kill()
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:
_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:
self._stdin.write(data)
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
self._process.terminate()
return
@ -509,7 +509,7 @@ class FFmpegOpusAudio(FFmpegAudio):
if isinstance(method, str):
probefunc = getattr(cls, '_probe_codec_' + method, 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:
fallback = cls._probe_codec_fallback
@ -526,18 +526,18 @@ class FFmpegOpusAudio(FFmpegAudio):
codec, bitrate = await loop.run_in_executor(None, lambda: probefunc(source, executable))
except Exception:
if not fallback:
_log.exception("Probe '%s' using '%s' failed", method, executable)
_log.exception("Probe '%s' using '%s' failed.", method, executable)
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:
codec, bitrate = await loop.run_in_executor(None, lambda: fallback(source, executable))
except Exception:
_log.exception("Fallback probe using '%s' failed", executable)
_log.exception("Fallback probe using '%s' failed.", executable)
else:
_log.debug("Fallback probe found codec=%s, bitrate=%s", codec, bitrate)
_log.debug('Fallback probe found codec=%s, bitrate=%s.', codec, bitrate)
else:
_log.debug("Probe found codec=%s, bitrate=%s", codec, bitrate)
_log.debug('Probe found codec=%s, bitrate=%s.', codec, bitrate)
finally:
return codec, bitrate

5
discord/profile.py

@ -34,7 +34,6 @@ from .enums import PremiumType, try_enum
from .flags import ApplicationFlags
from .member import Member
from .mixins import Hashable
from .object import Object
from .user import Note, User
if TYPE_CHECKING:
@ -96,9 +95,9 @@ class Profile:
state = self._state
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]]:
if mutual_friends is None:

12
discord/scheduled_event.py

@ -407,7 +407,9 @@ class ScheduledEvent(Hashable):
description: :class:`str`
The description of the scheduled event.
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
:attr:`EntityType.stage_instance`.
@ -426,7 +428,9 @@ class ScheduledEvent(Hashable):
privacy_level: :class:`PrivacyLevel`
The privacy level of the scheduled event.
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`
The new status of the scheduled event.
image: Optional[:class:`bytes`]
@ -467,7 +471,7 @@ class ScheduledEvent(Hashable):
if start_time is not MISSING:
if start_time.tzinfo is None:
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()
@ -476,7 +480,7 @@ class ScheduledEvent(Hashable):
if privacy_level is not MISSING:
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

71
discord/settings.py

@ -28,7 +28,7 @@ import base64
from datetime import datetime, timezone
import struct
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 discord_protos import PreloadedUserSettings # , FrecencyUserSettings
@ -125,8 +125,18 @@ class _ProtoSettings:
new.settings.CopyFrom(self.settings)
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)
if always_guild:
return self._state._get_or_create_unavailable_guild(id)
return self._state._get_guild(id) or Object(id=id)
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')
@property
def collapsed_emoji_picker_sections(self) -> Tuple[Union[EmojiPickerSection, Guild, Object], ...]:
"""Tuple[Union[:class:`EmojiPickerSection`, :class:`Guild`, :class:`Object`]]: A list of emoji picker sections (including guild IDs) that are collapsed."""
def collapsed_emoji_picker_sections(self) -> Tuple[Union[EmojiPickerSection, Guild], ...]:
"""Tuple[Union[:class:`EmojiPickerSection`, :class:`Guild`]]: A list of emoji picker sections (including guild IDs) that are collapsed."""
return tuple(
self._get_guild(section) if section.isdigit() else try_enum(EmojiPickerSection, section)
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], ...]:
"""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(
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
)
@ -497,8 +507,8 @@ class UserSettings(_ProtoSettings):
)
@property
def restricted_guilds(self) -> List[Union[Guild, Object]]:
"""List[Union[:class:`Guild`, :class:`Object`]]: A list of guilds that you will not receive DMs from."""
def restricted_guilds(self) -> List[Guild]:
"""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))
@property
@ -545,8 +555,8 @@ class UserSettings(_ProtoSettings):
return FriendDiscoveryFlags._from_value(self.settings.privacy.friend_discovery_flags.value)
@property
def activity_restricted_guilds(self) -> List[Union[Guild, Object]]:
"""List[Union[:class:`Guild`, :class:`Object`]]: A list of guilds that your current activity will not be shown in."""
def activity_restricted_guilds(self) -> List[Guild]:
"""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))
@property
@ -555,13 +565,13 @@ class UserSettings(_ProtoSettings):
return self.settings.privacy.default_guilds_activity_restricted
@property
def activity_joining_restricted_guilds(self) -> List[Union[Guild, Object]]:
"""List[Union[:class:`Guild`, :class:`Object`]]: A list of guilds that will not be able to join your current activity."""
def activity_joining_restricted_guilds(self) -> List[Guild]:
"""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))
@property
def message_request_restricted_guilds(self) -> List[Union[Guild, Object]]:
"""List[Union[:class:`Guild`, :class:`Object`]]: A list of guilds whose originating DMs will not be filtered into your message requests."""
def message_request_restricted_guilds(self) -> List[Guild]:
"""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))
@property
@ -683,8 +693,8 @@ class UserSettings(_ProtoSettings):
return [GuildFolder._from_settings(data=folder, state=state) for folder in self.settings.guild_folders.folders]
@property
def guild_positions(self) -> List[Union[Guild, Object]]:
"""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."""
def guild_positions(self) -> List[Guild]:
"""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))
# Favorites Settings
@ -1208,8 +1218,10 @@ class GuildFolder:
return self
def _get_guild(self, id, /) -> Union[Guild, Object]:
from .guild import Guild # circular import
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:
ret = {}
@ -1460,7 +1472,7 @@ class GuildProgress:
@property
def guild(self) -> Optional[Guild]:
"""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
def hub_progress(self) -> HubProgressFlags:
@ -1682,9 +1694,8 @@ class LegacyUserSettings:
def __repr__(self) -> str:
return '<LegacyUserSettings>'
def _get_guild(self, id: int, /) -> Union[Guild, Object]:
id = int(id)
return self._state._get_guild(id) or Object(id=id)
def _get_guild(self, id: int, /) -> Guild:
return self._state._get_or_create_unavailable_guild(int(id))
def _update(self, data: Dict[str, Any]) -> None:
RAW_VALUES = {
@ -1824,16 +1835,16 @@ class LegacyUserSettings:
return await self._state.client.edit_legacy_settings(**kwargs)
@property
def activity_restricted_guilds(self) -> List[Union[Guild, Object]]:
"""List[Union[:class:`Guild`, :class:`Object`]]: A list of guilds that your current activity will not be shown in.
def activity_restricted_guilds(self) -> List[Guild]:
"""List[:class:`Guild`]: A list of guilds that your current activity will not be shown in.
.. versionadded:: 2.0
"""
return list(map(self._get_guild, getattr(self, '_activity_restricted_guild_ids', [])))
@property
def activity_joining_restricted_guilds(self) -> List[Union[Guild, Object]]:
"""List[Union[:class:`Guild`, :class:`Object`]]: A list of guilds that will not be able to join your current activity.
def activity_joining_restricted_guilds(self) -> List[Guild]:
"""List[:class:`Guild`]: A list of guilds that will not be able to join your current activity.
.. versionadded:: 2.0
"""
@ -1873,8 +1884,8 @@ class LegacyUserSettings:
]
@property
def guild_positions(self) -> List[Union[Guild, Object]]:
"""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."""
def guild_positions(self) -> List[Guild]:
"""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', [])))
@property
@ -1893,8 +1904,8 @@ class LegacyUserSettings:
return getattr(self, '_passwordless', False)
@property
def restricted_guilds(self) -> List[Union[Guild, Object]]:
"""List[Union[:class:`Guild`, :class:`Object`]]: A list of guilds that you will not receive DMs from."""
def restricted_guilds(self) -> List[Guild]:
"""List[:class:`Guild`]: A list of guilds that you will not receive DMs from."""
return list(map(self._get_guild, getattr(self, '_restricted_guilds', [])))
@property
@ -2007,7 +2018,7 @@ class ChannelSettings:
@property
def channel(self) -> Union[GuildChannel, PrivateChannel]:
"""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:
channel = guild.get_channel(self._channel_id)
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 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
@property

2
discord/state.py

@ -2631,7 +2631,7 @@ class ConnectionState:
# 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]]:
if isinstance(channel, (TextChannel, Thread, VoiceChannel)):
if isinstance(channel, (TextChannel, Thread, VoiceChannel, StageChannel)):
return channel.guild.get_member(user_id)
return self.get_user(user_id)

2
discord/team.py

@ -412,7 +412,7 @@ class Team(Hashable):
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
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
def jump_url(self) -> str:
""":class:`str`: Returns a URL that allows the client to jump to the thread.
.. versionadded:: 2.0
"""
""":class:`str`: Returns a URL that allows the client to jump to the thread."""
return f'https://discord.com/channels/{self.guild.id}/{self.id}'
@property
@ -277,17 +274,14 @@ class Thread(Messageable, Hashable):
@property
def applied_tags(self) -> List[ForumTag]:
"""List[:class:`ForumTag`]: A list of tags applied to this thread.
.. versionadded:: 2.0
"""
"""List[:class:`ForumTag`]: A list of tags applied to this thread."""
tags = []
if self.parent is None or self.parent.type != ChannelType.forum:
return tags
parent = self.parent
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:
tags.append(tag)
@ -607,8 +601,6 @@ class Thread(Messageable, Hashable):
A value of ``0`` disables slowmode. The maximum value possible is ``21600``.
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.
.. versionadded:: 2.0
reason: Optional[:class:`str`]
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`.
.. versionadded:: 2.0
Parameters
-----------
\*tags: :class:`abc.Snowflake`
@ -696,8 +686,6 @@ class Thread(Messageable, Hashable):
The parent channel must be a :class:`ForumChannel`.
.. versionadded:: 2.0
Parameters
-----------
\*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
doing an unnecessary API call.
.. versionadded:: 2.0
Parameters
------------
message_id: :class:`int`

6
discord/types/message.py

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

18
discord/webhook/async_.py

@ -43,7 +43,7 @@ from ..user import BaseUser, User
from ..flags import MessageFlags
from ..asset import Asset
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 ..channel import TextChannel, ForumChannel, PartialMessageable
from ..file import File
@ -800,7 +800,7 @@ class BaseWebhook(Hashable):
@property
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``.
"""
@ -957,7 +957,7 @@ class Webhook(BaseWebhook):
*,
session: aiohttp.ClientSession = MISSING,
client: Client = MISSING,
bot_token: Optional[str] = None,
user_token: Optional[str] = None,
) -> Self:
"""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.
.. versionadded:: 2.0
bot_token: Optional[:class:`str`]
The bot authentication token for authenticated requests
user_token: Optional[:class:`str`]
The user authentication token for authenticated requests
involving the webhook.
.. versionadded:: 2.0
@ -1011,7 +1011,7 @@ class Webhook(BaseWebhook):
if session is MISSING:
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
def from_url(
@ -1020,7 +1020,7 @@ class Webhook(BaseWebhook):
*,
session: aiohttp.ClientSession = MISSING,
client: Client = MISSING,
bot_token: Optional[str] = None,
user_token: Optional[str] = None,
) -> Self:
"""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.
.. versionadded:: 2.0
bot_token: Optional[:class:`str`]
user_token: Optional[:class:`str`]
The bot authentication token for authenticated requests
involving the webhook.
@ -1078,7 +1078,7 @@ class Webhook(BaseWebhook):
data: Dict[str, Any] = m.groupdict()
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
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}'
@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`.
Parameters
@ -629,7 +629,7 @@ class SyncWebhook(BaseWebhook):
that the library does not manage the session and
will not close it. If not given, the ``requests``
auto session creation functions are used instead.
bot_token: Optional[:class:`str`]
user_token: Optional[:class:`str`]
The bot authentication token for authenticated requests
involving the webhook.
@ -651,10 +651,10 @@ class SyncWebhook(BaseWebhook):
raise TypeError(f'expected requests.Session not {session.__class__!r}')
else:
session = requests # type: ignore
return cls(data, session, token=bot_token)
return cls(data, session, token=user_token)
@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.
Parameters
@ -666,7 +666,7 @@ class SyncWebhook(BaseWebhook):
that the library does not manage the session and
will not close it. If not given, the ``requests``
auto session creation functions are used instead.
bot_token: Optional[:class:`str`]
user_token: Optional[:class:`str`]
The bot authentication token for authenticated requests
involving the webhook.
@ -694,7 +694,7 @@ class SyncWebhook(BaseWebhook):
raise TypeError(f'expected requests.Session not {session.__class__!r}')
else:
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:
"""Fetches the current webhook.

8
discord/welcome_screen.py

@ -62,7 +62,9 @@ class WelcomeChannel:
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.description = description
@ -129,7 +131,9 @@ class WelcomeScreen:
state = self.guild._state
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', '')
def __repr__(self) -> str:

29
docs/api.rst

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

2
docs/faq.rst

@ -341,7 +341,7 @@ Quick example:
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

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:
- :class:`VoiceChannel` is now :class:`abc.Messageable` so it can have messages sent and received.
- :attr:`Message.channel` can now be :class:`VoiceChannel`.
In the future this may include :class:`StageChannel` when Discord implements it.
- :attr:`Message.channel` can now be :class:`VoiceChannel` and :class:`StageChannel`.
Removal of ``StoreChannel``
-----------------------------

12
setup.py

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

Loading…
Cancel
Save