Skip to content

Commit

Permalink
Merge branch 'master' into feat/embeds
Browse files Browse the repository at this point in the history
  • Loading branch information
Snipy7374 authored Sep 14, 2024
2 parents 81c60ec + a34d0f9 commit 980f1f1
Show file tree
Hide file tree
Showing 17 changed files with 112 additions and 105 deletions.
4 changes: 2 additions & 2 deletions changelog/1175.feature.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Add the new poll discord API feature. This includes the following new classes and events:

- New types: :class:`Poll`, :class:`PollAnswer`, :class:`PollMedia`, :class:`RawMessagePollVoteActionEvent` and :class:`PollLayoutType`.
- New types: :class:`Poll`, :class:`PollAnswer`, :class:`PollMedia`, :class:`RawPollVoteActionEvent` and :class:`PollLayoutType`.
- Edited :meth:`abc.Messageable.send`, :meth:`Webhook.send`, :meth:`ext.commands.Context.send` and :meth:`disnake.InteractionResponse.send_message` to be able to send polls.
- Edited :class:`Message` to store a new :attr:`Message.poll` attribute for polls.
- Edited :class:`Event` to contain the new :func:`on_message_poll_vote_add`, :func:`on_message_poll_vote_remove`, :func:`on_raw_message_poll_vote_add` and :func:`on_raw_message_poll_vote_remove`.
- Edited :class:`Event` to contain the new :func:`on_poll_vote_add`, :func:`on_poll_vote_remove`, :func:`on_raw_poll_vote_add` and :func:`on_raw_poll_vote_remove`.
2 changes: 2 additions & 0 deletions changelog/1233.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
For interactions in private channels, :attr:`Interaction.channel` is now always a proper :class:`DMChannel` or :class:`GroupChannel` object, instead of :class:`PartialMessageable`.
- This also applies to other channel objects in interactions, e.g. channel parameters in slash commands, or :attr:`ui.ChannelSelect.values`.
9 changes: 6 additions & 3 deletions disnake/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@
from typing_extensions import Self

from .asset import Asset
from .channel import CategoryChannel, DMChannel, PartialMessageable
from .channel import CategoryChannel, DMChannel, GroupChannel, PartialMessageable
from .client import Client
from .embeds import Embed
from .emoji import Emoji
from .enums import InviteTarget
from .guild import Guild, GuildMessageable
from .guild import Guild, GuildChannel as AnyGuildChannel, GuildMessageable
from .guild_scheduled_event import GuildScheduledEvent
from .iterators import HistoryIterator
from .member import Member
Expand All @@ -90,7 +90,10 @@
from .user import ClientUser
from .voice_region import VoiceRegion

MessageableChannel = Union[GuildMessageable, DMChannel, PartialMessageable]
MessageableChannel = Union[GuildMessageable, DMChannel, GroupChannel, PartialMessageable]
# include non-messageable channels, e.g. category/forum
AnyChannel = Union[MessageableChannel, AnyGuildChannel]

SnowflakeTime = Union["Snowflake", datetime]

MISSING = utils.MISSING
Expand Down
9 changes: 7 additions & 2 deletions disnake/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -4663,7 +4663,10 @@ class DMChannel(disnake.abc.Messageable, Hashable):

def __init__(self, *, me: ClientUser, state: ConnectionState, data: DMChannelPayload) -> None:
self._state: ConnectionState = state
self.recipient: Optional[User] = state.store_user(data["recipients"][0]) # type: ignore
self.recipient: Optional[User] = None
if recipients := data.get("recipients"):
self.recipient = state.store_user(recipients[0]) # type: ignore

self.me: ClientUser = me
self.id: int = int(data["id"])
self.last_pin_timestamp: Optional[datetime.datetime] = utils.parse_time(
Expand Down Expand Up @@ -4803,8 +4806,10 @@ class GroupChannel(disnake.abc.Messageable, Hashable):
----------
recipients: List[:class:`User`]
The users you are participating with in the group channel.
If this channel is received through the gateway, the recipient information
may not be always available.
me: :class:`ClientUser`
The user presenting yourself.
The user representing yourself.
id: :class:`int`
The group channel ID.
owner: Optional[:class:`User`]
Expand Down
27 changes: 15 additions & 12 deletions disnake/ext/commands/cog.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

from disnake.interactions import ApplicationCommandInteraction

from ._types import MaybeCoro
from .bot import AutoShardedBot, AutoShardedInteractionBot, Bot, InteractionBot
from .context import Context
from .core import Command
Expand Down Expand Up @@ -491,7 +492,7 @@ def cog_unload(self) -> None:
pass

@_cog_special_method
def bot_check_once(self, ctx: Context) -> bool:
def bot_check_once(self, ctx: Context) -> MaybeCoro[bool]:
"""A special method that registers as a :meth:`.Bot.check_once`
check.
Expand All @@ -503,7 +504,7 @@ def bot_check_once(self, ctx: Context) -> bool:
return True

@_cog_special_method
def bot_check(self, ctx: Context) -> bool:
def bot_check(self, ctx: Context) -> MaybeCoro[bool]:
"""A special method that registers as a :meth:`.Bot.check`
check.
Expand All @@ -515,7 +516,7 @@ def bot_check(self, ctx: Context) -> bool:
return True

@_cog_special_method
def bot_slash_command_check_once(self, inter: ApplicationCommandInteraction) -> bool:
def bot_slash_command_check_once(self, inter: ApplicationCommandInteraction) -> MaybeCoro[bool]:
"""A special method that registers as a :meth:`.Bot.slash_command_check_once`
check.
Expand All @@ -525,7 +526,7 @@ def bot_slash_command_check_once(self, inter: ApplicationCommandInteraction) ->
return True

@_cog_special_method
def bot_slash_command_check(self, inter: ApplicationCommandInteraction) -> bool:
def bot_slash_command_check(self, inter: ApplicationCommandInteraction) -> MaybeCoro[bool]:
"""A special method that registers as a :meth:`.Bot.slash_command_check`
check.
Expand All @@ -535,27 +536,29 @@ def bot_slash_command_check(self, inter: ApplicationCommandInteraction) -> bool:
return True

@_cog_special_method
def bot_user_command_check_once(self, inter: ApplicationCommandInteraction) -> bool:
def bot_user_command_check_once(self, inter: ApplicationCommandInteraction) -> MaybeCoro[bool]:
"""Similar to :meth:`.Bot.slash_command_check_once` but for user commands."""
return True

@_cog_special_method
def bot_user_command_check(self, inter: ApplicationCommandInteraction) -> bool:
def bot_user_command_check(self, inter: ApplicationCommandInteraction) -> MaybeCoro[bool]:
"""Similar to :meth:`.Bot.slash_command_check` but for user commands."""
return True

@_cog_special_method
def bot_message_command_check_once(self, inter: ApplicationCommandInteraction) -> bool:
def bot_message_command_check_once(
self, inter: ApplicationCommandInteraction
) -> MaybeCoro[bool]:
"""Similar to :meth:`.Bot.slash_command_check_once` but for message commands."""
return True

@_cog_special_method
def bot_message_command_check(self, inter: ApplicationCommandInteraction) -> bool:
def bot_message_command_check(self, inter: ApplicationCommandInteraction) -> MaybeCoro[bool]:
"""Similar to :meth:`.Bot.slash_command_check` but for message commands."""
return True

@_cog_special_method
def cog_check(self, ctx: Context) -> bool:
def cog_check(self, ctx: Context) -> MaybeCoro[bool]:
"""A special method that registers as a :func:`~.check`
for every text command and subcommand in this cog.
Expand All @@ -567,7 +570,7 @@ def cog_check(self, ctx: Context) -> bool:
return True

@_cog_special_method
def cog_slash_command_check(self, inter: ApplicationCommandInteraction) -> bool:
def cog_slash_command_check(self, inter: ApplicationCommandInteraction) -> MaybeCoro[bool]:
"""A special method that registers as a :func:`~.check`
for every slash command and subcommand in this cog.
Expand All @@ -577,12 +580,12 @@ def cog_slash_command_check(self, inter: ApplicationCommandInteraction) -> bool:
return True

@_cog_special_method
def cog_user_command_check(self, inter: ApplicationCommandInteraction) -> bool:
def cog_user_command_check(self, inter: ApplicationCommandInteraction) -> MaybeCoro[bool]:
"""Similar to :meth:`.Cog.cog_slash_command_check` but for user commands."""
return True

@_cog_special_method
def cog_message_command_check(self, inter: ApplicationCommandInteraction) -> bool:
def cog_message_command_check(self, inter: ApplicationCommandInteraction) -> MaybeCoro[bool]:
"""Similar to :meth:`.Cog.cog_slash_command_check` but for message commands."""
return True

Expand Down
4 changes: 2 additions & 2 deletions disnake/ext/commands/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
if TYPE_CHECKING:
from typing_extensions import ParamSpec

from disnake.channel import DMChannel
from disnake.channel import DMChannel, GroupChannel
from disnake.guild import Guild, GuildMessageable
from disnake.member import Member
from disnake.state import ConnectionState
Expand Down Expand Up @@ -262,7 +262,7 @@ def guild(self) -> Optional[Guild]:
return self.message.guild

@disnake.utils.cached_property
def channel(self) -> Union[GuildMessageable, DMChannel]:
def channel(self) -> Union[GuildMessageable, DMChannel, GroupChannel]:
"""Union[:class:`.abc.Messageable`]: Returns the channel associated with this context's command.
Shorthand for :attr:`.Message.channel`.
"""
Expand Down
8 changes: 5 additions & 3 deletions disnake/interactions/application_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,18 @@ class ApplicationCommandInteraction(Interaction[ClientT]):
The application ID that the interaction was for.
guild_id: Optional[:class:`int`]
The guild ID the interaction was sent from.
channel: Union[:class:`abc.GuildChannel`, :class:`Thread`, :class:`PartialMessageable`]
channel: Union[:class:`abc.GuildChannel`, :class:`Thread`, :class:`abc.PrivateChannel`, :class:`PartialMessageable`]
The channel the interaction was sent from.
Note that due to a Discord limitation, DM channels
are not resolved as there is no data to complete them.
These are :class:`PartialMessageable` instead.
may not contain recipient information.
Unknown channel types will be :class:`PartialMessageable`.
.. versionchanged:: 2.10
If the interaction was sent from a thread and the bot cannot normally access the thread,
this is now a proper :class:`Thread` object.
Private channels are now proper :class:`DMChannel`/:class:`GroupChannel`
objects instead of :class:`PartialMessageable`.
.. note::
If you want to compute the interaction author's or bot's permissions in the channel,
Expand Down
47 changes: 20 additions & 27 deletions disnake/interactions/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,12 @@

from aiohttp import ClientSession

from ..abc import MessageableChannel
from ..abc import AnyChannel, MessageableChannel
from ..app_commands import Choices
from ..client import Client
from ..embeds import Embed
from ..ext.commands import AutoShardedBot, Bot
from ..file import File
from ..guild import GuildChannel, GuildMessageable
from ..mentions import AllowedMentions
from ..poll import Poll
from ..state import ConnectionState
Expand All @@ -88,9 +87,6 @@
from .message import MessageInteraction
from .modal import ModalInteraction

InteractionMessageable = Union[GuildMessageable, PartialMessageable]
InteractionChannel = Union[InteractionMessageable, GuildChannel]

AnyBot = Union[Bot, AutoShardedBot]


Expand Down Expand Up @@ -130,16 +126,18 @@ class Interaction(Generic[ClientT]):
.. versionchanged:: 2.5
Changed to :class:`Locale` instead of :class:`str`.
channel: Union[:class:`abc.GuildChannel`, :class:`Thread`, :class:`PartialMessageable`]
channel: Union[:class:`abc.GuildChannel`, :class:`Thread`, :class:`abc.PrivateChannel`, :class:`PartialMessageable`]
The channel the interaction was sent from.
Note that due to a Discord limitation, DM channels
are not resolved as there is no data to complete them.
These are :class:`PartialMessageable` instead.
may not contain recipient information.
Unknown channel types will be :class:`PartialMessageable`.
.. versionchanged:: 2.10
If the interaction was sent from a thread and the bot cannot normally access the thread,
this is now a proper :class:`Thread` object.
Private channels are now proper :class:`DMChannel`/:class:`GroupChannel`
objects instead of :class:`PartialMessageable`.
.. note::
If you want to compute the interaction author's or bot's permissions in the channel,
Expand Down Expand Up @@ -236,7 +234,7 @@ def __init__(self, *, data: InteractionPayload, state: ConnectionState) -> None:
self.author = self._state.store_user(user)

# TODO: consider making this optional in 3.0
self.channel: InteractionMessageable = state._get_partial_interaction_channel(
self.channel: MessageableChannel = state._get_partial_interaction_channel(
data["channel"], guild_fallback, return_messageable=True
)

Expand Down Expand Up @@ -1866,7 +1864,7 @@ class InteractionDataResolved(Dict[str, Any]):
A mapping of IDs to users.
roles: Dict[:class:`int`, :class:`Role`]
A mapping of IDs to roles.
channels: Dict[:class:`int`, Union[:class:`abc.GuildChannel`, :class:`Thread`, :class:`PartialMessageable`]]
channels: Dict[:class:`int`, Union[:class:`abc.GuildChannel`, :class:`Thread`, :class:`abc.PrivateChannel`, :class:`PartialMessageable`]]
A mapping of IDs to partial channels (only ``id``, ``name`` and ``permissions`` are included,
threads also have ``thread_metadata`` and ``parent_id``).
messages: Dict[:class:`int`, :class:`Message`]
Expand All @@ -1891,7 +1889,7 @@ def __init__(
self.members: Dict[int, Member] = {}
self.users: Dict[int, User] = {}
self.roles: Dict[int, Role] = {}
self.channels: Dict[int, InteractionChannel] = {}
self.channels: Dict[int, AnyChannel] = {}
self.messages: Dict[int, Message] = {}
self.attachments: Dict[int, Attachment] = {}

Expand Down Expand Up @@ -1946,25 +1944,20 @@ def __init__(
channel_id = int(message["channel_id"])
channel: Optional[MessageableChannel] = None

if (
channel_id == parent.channel.id
# we still want to fall back to state.get_channel when the
# parent channel is a dm/group channel, for now.
# FIXME: remove this once `parent.channel` supports `DMChannel`
and not isinstance(parent.channel, PartialMessageable)
):
if channel_id == parent.channel.id:
# fast path, this should generally be the case
channel = parent.channel
else:
# in case this ever happens, fall back to guild channel cache
channel = cast(
"Optional[MessageableChannel]",
(guild and guild.get_channel(channel_id) or state.get_channel(channel_id)),
(guild and guild.get_channel(channel_id)),
)

if channel is None:
# n.b. the message's channel is not sent as part of `resolved.channels`,
# so we need to fall back to partials here.
channel = PartialMessageable(state=state, id=channel_id, type=None)
if channel is None:
# n.b. the message's channel is not sent as part of `resolved.channels`,
# so we need to fall back to partials here.
channel = PartialMessageable(state=state, id=channel_id, type=None)

self.messages[int(str_id)] = Message(state=state, channel=channel, data=message)

Expand All @@ -1980,18 +1973,18 @@ def __repr__(self) -> str:
@overload
def get_with_type(
self, key: Snowflake, data_type: Union[OptionType, ComponentType]
) -> Union[Member, User, Role, InteractionChannel, Message, Attachment, None]:
) -> Union[Member, User, Role, AnyChannel, Message, Attachment, None]:
...

@overload
def get_with_type(
self, key: Snowflake, data_type: Union[OptionType, ComponentType], default: T
) -> Union[Member, User, Role, InteractionChannel, Message, Attachment, T]:
) -> Union[Member, User, Role, AnyChannel, Message, Attachment, T]:
...

def get_with_type(
self, key: Snowflake, data_type: Union[OptionType, ComponentType], default: T = None
) -> Union[Member, User, Role, InteractionChannel, Message, Attachment, T, None]:
) -> Union[Member, User, Role, AnyChannel, Message, Attachment, T, None]:
if data_type is OptionType.mentionable or data_type is ComponentType.mentionable_select:
key = int(key)
if (result := self.members.get(key)) is not None:
Expand Down Expand Up @@ -2019,7 +2012,7 @@ def get_with_type(

def get_by_id(
self, key: Optional[int]
) -> Optional[Union[Member, User, Role, InteractionChannel, Message, Attachment]]:
) -> Optional[Union[Member, User, Role, AnyChannel, Message, Attachment]]:
if key is None:
return None

Expand Down
14 changes: 8 additions & 6 deletions disnake/interactions/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
)

if TYPE_CHECKING:
from ..abc import AnyChannel
from ..member import Member
from ..role import Role
from ..state import ConnectionState
Expand All @@ -25,7 +26,6 @@
MessageInteraction as MessageInteractionPayload,
)
from ..user import User
from .base import InteractionChannel


class MessageInteraction(Interaction[ClientT]):
Expand All @@ -47,16 +47,18 @@ class MessageInteraction(Interaction[ClientT]):
The token to continue the interaction. These are valid for 15 minutes.
guild_id: Optional[:class:`int`]
The guild ID the interaction was sent from.
channel: Union[:class:`abc.GuildChannel`, :class:`Thread`, :class:`PartialMessageable`]
channel: Union[:class:`abc.GuildChannel`, :class:`Thread`, :class:`abc.PrivateChannel`, :class:`PartialMessageable`]
The channel the interaction was sent from.
Note that due to a Discord limitation, DM channels
are not resolved as there is no data to complete them.
These are :class:`PartialMessageable` instead.
may not contain recipient information.
Unknown channel types will be :class:`PartialMessageable`.
.. versionchanged:: 2.10
If the interaction was sent from a thread and the bot cannot normally access the thread,
this is now a proper :class:`Thread` object.
Private channels are now proper :class:`DMChannel`/:class:`GroupChannel`
objects instead of :class:`PartialMessageable`.
.. note::
If you want to compute the interaction author's or bot's permissions in the channel,
Expand Down Expand Up @@ -116,7 +118,7 @@ def values(self) -> Optional[List[str]]:
@cached_slot_property("_cs_resolved_values")
def resolved_values(
self,
) -> Optional[Sequence[Union[str, Member, User, Role, InteractionChannel]]]:
) -> Optional[Sequence[Union[str, Member, User, Role, AnyChannel]]]:
"""Optional[Sequence[:class:`str`, :class:`Member`, :class:`User`, :class:`Role`, Union[:class:`abc.GuildChannel`, :class:`Thread`, :class:`PartialMessageable`]]]: The (resolved) values the user selected.
For select menus of type :attr:`~ComponentType.string_select`,
Expand All @@ -134,7 +136,7 @@ def resolved_values(
return self.data.values

resolved = self.data.resolved
values: List[Union[Member, User, Role, InteractionChannel]] = []
values: List[Union[Member, User, Role, AnyChannel]] = []
for key in self.data.values:
# force upcast to avoid typing issues; we expect the api to only provide valid values
value: Any = resolved.get_with_type(key, component_type, key)
Expand Down
Loading

0 comments on commit 980f1f1

Please sign in to comment.