Skip to content

Commit

Permalink
Merge branch 'master' into feat/poll_msg
Browse files Browse the repository at this point in the history
  • Loading branch information
Snipy7374 authored Sep 14, 2024
2 parents 70c6510 + a34d0f9 commit f5054c8
Show file tree
Hide file tree
Showing 16 changed files with 97 additions and 93 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
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
8 changes: 5 additions & 3 deletions disnake/interactions/modal.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,18 @@ class ModalInteraction(Interaction[ClientT]):
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
6 changes: 3 additions & 3 deletions disnake/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
from typing_extensions import Self

from .abc import GuildChannel, MessageableChannel, Snowflake
from .channel import DMChannel
from .channel import DMChannel, GroupChannel
from .guild import GuildMessageable
from .mentions import AllowedMentions
from .role import Role
Expand Down Expand Up @@ -1007,7 +1007,7 @@ def __init__(
self.activity: Optional[MessageActivityPayload] = data.get("activity")
# for user experience, on_message has no business getting partials
# TODO: Subscripted message to include the channel
self.channel: Union[GuildMessageable, DMChannel] = channel # type: ignore
self.channel: Union[GuildMessageable, DMChannel, GroupChannel] = channel # type: ignore
self.position: Optional[int] = data.get("position", None)
self._edited_timestamp: Optional[datetime.datetime] = utils.parse_time(
data["edited_timestamp"]
Expand Down Expand Up @@ -2287,7 +2287,7 @@ class PartialMessage(Hashable):
Attributes
----------
channel: Union[:class:`TextChannel`, :class:`Thread`, :class:`DMChannel`, :class:`VoiceChannel`, :class:`StageChannel`, :class:`PartialMessageable`]
channel: Union[:class:`TextChannel`, :class:`VoiceChannel`, :class:`StageChannel`, :class:`Thread`, :class:`DMChannel`, :class:`GroupChannel`, :class:`PartialMessageable`]
The channel associated with this partial message.
id: :class:`int`
The message ID.
Expand Down
Loading

0 comments on commit f5054c8

Please sign in to comment.