from __future__ import annotations
import logging
import typing as t
import hikari
from hikari.snowflakes import Snowflake
__all__: t.Sequence[str] = ("PartialContext", "InteractionResponse")
logger = logging.getLogger("__name__")
T = t.TypeVar("T", bound=hikari.ComponentInteraction | hikari.ModalInteraction)
[docs]
class InteractionResponse:
"""
Represents a response to an interaction, allows for standardized handling
of responses. This class is not meant to be directly instantiated, and is
instead returned by `flare.context.PartialContext`.
"""
__slots__ = (
"_context",
"_message",
)
def __init__(self, context: PartialContext[t.Any], message: t.Optional[hikari.Message] = None) -> None:
self._context: PartialContext[t.Any] = context
self._message: t.Optional[hikari.Message] = message
def __await__(self) -> t.Generator[t.Any, None, hikari.Message]:
return self.retrieve_message().__await__()
[docs]
async def retrieve_message(self) -> hikari.Message:
"""Get or fetch the message created by this response.
Initial responses need to be fetched, while followups will be provided directly.
> ℹ️
The object itself can also be awaited directly, which in turn calls this method,
producing the same results.
Returns:
hikari.Message: The message created by this response.
"""
if self._message:
return self._message
return await self._context.interaction.fetch_initial_response()
[docs]
async def delete(self) -> None:
"""Delete the response issued to the interaction this object represents."""
if self._message:
await self._context.interaction.delete_message(self._message)
await self._context.interaction.delete_initial_response()
[docs]
async def edit(
self,
content: hikari.UndefinedOr[t.Any] = hikari.UNDEFINED,
*,
component: hikari.UndefinedOr[hikari.api.ComponentBuilder] = hikari.UNDEFINED,
components: hikari.UndefinedOr[t.Sequence[hikari.api.ComponentBuilder]] = hikari.UNDEFINED,
attachment: hikari.UndefinedNoneOr[hikari.Resourceish] = hikari.UNDEFINED,
attachments: hikari.UndefinedNoneOr[t.Sequence[hikari.Resourceish]] = hikari.UNDEFINED,
embed: hikari.UndefinedOr[hikari.Embed] = hikari.UNDEFINED,
embeds: hikari.UndefinedOr[t.Sequence[hikari.Embed]] = hikari.UNDEFINED,
mentions_everyone: hikari.UndefinedOr[bool] = hikari.UNDEFINED,
user_mentions: hikari.UndefinedOr[
t.Union[hikari.SnowflakeishSequence[hikari.PartialUser], bool]
] = hikari.UNDEFINED,
role_mentions: hikari.UndefinedOr[
t.Union[hikari.SnowflakeishSequence[hikari.PartialRole], bool]
] = hikari.UNDEFINED,
) -> InteractionResponse:
"""A short-hand method to edit the message belonging to this response.
Args:
content:
The content of the message. Anything passed here will be cast to str.
attachment:
An attachment to add to this message.
attachments:
A sequence of attachments to add to this message.
component:
A component to add to this message.
components:
A sequence of components to add to this message.
embed:
An embed to add to this message.
embeds:
A sequence of embeds to add to this message.
mentions_everyone:
If True, mentioning @everyone will be allowed.
user_mentions:
The set of allowed user mentions in this message. Set to True to allow all.
role_mentions:
The set of allowed role mentions in this message. Set to True to allow all.
Returns:
InteractionResponse: A proxy object representing the response to the interaction.
"""
if self._message:
message = await self._context.interaction.edit_message(
self._message,
content,
component=component,
components=components,
attachment=attachment,
attachments=attachments,
embed=embed,
embeds=embeds,
mentions_everyone=mentions_everyone,
user_mentions=user_mentions,
role_mentions=role_mentions,
)
return self._context._create_response(message)
message = await self._context.interaction.edit_initial_response(
content,
component=component,
components=components,
attachment=attachment,
attachments=attachments,
embed=embed,
embeds=embeds,
mentions_everyone=mentions_everyone,
user_mentions=user_mentions,
role_mentions=role_mentions,
)
return self._context._create_response()
[docs]
class PartialContext(t.Generic[T]):
"""A context object proxying a Discord interaction."""
__slots__ = ("_interaction", "_responses", "_issued_response")
def __init__(self, interaction: T) -> None:
self._interaction: T = interaction
self._responses: t.MutableSequence[InteractionResponse] = []
self._issued_response: bool = False
@property
def interaction(self) -> T:
"""The underlying interaction object."""
return self._interaction
@property
def custom_id(self) -> str:
"""The developer provided unique identifier for the interaction this context is proxying."""
return self._interaction.custom_id
@property
def responses(self) -> t.Sequence[InteractionResponse]:
"""A list of all responses issued to the interaction this context is proxying."""
return self._responses
@property
def app(self) -> hikari.RESTAware:
"""The application that received the interaction."""
return self._interaction.app
@property
def bot(self) -> hikari.RESTAware:
"""The application that received the interaction."""
return self.app
@property
def user(self) -> hikari.User:
"""The user who triggered this interaction."""
return self._interaction.user
@property
def author(self) -> hikari.User:
"""Alias for PartialContext.user"""
return self.user
@property
def member(self) -> t.Optional[hikari.InteractionMember]:
"""The member who triggered this interaction. Will be None in DMs."""
return self._interaction.member
@property
def locale(self) -> t.Union[str, hikari.Locale]:
"""The locale of this context."""
return self._interaction.locale
@property
def guild_locale(self) -> t.Optional[t.Union[str, hikari.Locale]]:
"""
The guild locale of this context, if in a guild.
This will default to `en-US` if not a community guild.
"""
return self._interaction.guild_locale
@property
def app_permissions(self) -> t.Optional[hikari.Permissions]:
"""The permissions of the user who triggered the interaction. Will be None in DMs."""
return self._interaction.app_permissions
@property
def channel_id(self) -> Snowflake:
"""The ID of the channel the context represents."""
return self._interaction.channel_id
@property
def guild_id(self) -> t.Optional[Snowflake]:
"""The ID of the guild the context represents. Will be None in DMs."""
return self._interaction.guild_id
def _create_response(self, message: t.Optional[hikari.Message] = None) -> InteractionResponse:
"""Create a new response and add it to the list of tracked responses."""
if not message:
# If a message was not passed this is an initial response
self._issued_response = True
response = InteractionResponse(self, message)
self._responses.append(response)
return response
[docs]
def get_guild(self) -> t.Optional[hikari.GatewayGuild]:
"""Gets the guild this context represents, if any. Requires application cache."""
return self._interaction.get_guild()
[docs]
def get_channel(self) -> t.Optional[hikari.TextableGuildChannel]:
"""Gets the channel this context represents, None if in a DM. Requires application cache."""
return self._interaction.get_channel()
[docs]
async def get_last_response(self) -> InteractionResponse:
"""Get the last response issued to the interaction this context is proxying.
Returns:
InteractionResponse: The response object.
Raises:
RuntimeError: The interaction was not yet responded to.
"""
if self._responses:
return self._responses[-1]
raise RuntimeError("This interaction was not yet issued a response.")
[docs]
async def respond(
self,
content: hikari.UndefinedOr[t.Any] = hikari.UNDEFINED,
*,
flags: t.Union[int, hikari.MessageFlag, hikari.UndefinedType] = hikari.UNDEFINED,
tts: hikari.UndefinedOr[bool] = hikari.UNDEFINED,
component: hikari.UndefinedOr[hikari.api.ComponentBuilder] = hikari.UNDEFINED,
components: hikari.UndefinedOr[t.Sequence[hikari.api.ComponentBuilder]] = hikari.UNDEFINED,
attachment: hikari.UndefinedOr[hikari.Resourceish] = hikari.UNDEFINED,
attachments: hikari.UndefinedOr[t.Sequence[hikari.Resourceish]] = hikari.UNDEFINED,
embed: hikari.UndefinedOr[hikari.Embed] = hikari.UNDEFINED,
embeds: hikari.UndefinedOr[t.Sequence[hikari.Embed]] = hikari.UNDEFINED,
mentions_everyone: hikari.UndefinedOr[bool] = hikari.UNDEFINED,
user_mentions: hikari.UndefinedOr[
t.Union[hikari.SnowflakeishSequence[hikari.PartialUser], bool]
] = hikari.UNDEFINED,
role_mentions: hikari.UndefinedOr[
t.Union[hikari.SnowflakeishSequence[hikari.PartialRole], bool]
] = hikari.UNDEFINED,
) -> InteractionResponse:
"""Short-hand method to create a new message response via the interaction this context represents.
Args:
content:
The content of the message. Anything passed here will be cast to str.
tts:
If the message should be tts or not.
attachment:
An attachment to add to this message.
attachments:
A sequence of attachments to add to this message.
component:
A component to add to this message.
components:
A sequence of components to add to this message.
embed:
An embed to add to this message.
embeds:
A sequence of embeds to add to this message.
mentions_everyone:
If True, mentioning @everyone will be allowed.
user_mentions:
The set of allowed user mentions in this message. Set to True to allow all.
role_mentions:
The set of allowed role mentions in this message. Set to True to allow all.
flags:
Message flags that should be included with this message.
Returns:
InteractionResponse: A proxy object representing the response to the interaction.
"""
if self._issued_response:
message = await self.interaction.execute(
content,
tts=tts,
component=component,
components=components,
attachment=attachment,
attachments=attachments,
embed=embed,
embeds=embeds,
mentions_everyone=mentions_everyone,
user_mentions=user_mentions,
role_mentions=role_mentions,
flags=flags,
)
response = self._create_response(message)
else:
await self.interaction.create_initial_response(
hikari.ResponseType.MESSAGE_CREATE,
content,
tts=tts,
component=component,
components=components,
attachment=attachment,
attachments=attachments,
embed=embed,
embeds=embeds,
mentions_everyone=mentions_everyone,
user_mentions=user_mentions,
role_mentions=role_mentions,
flags=flags,
)
response = self._create_response()
return response
[docs]
async def edit_response(
self,
content: hikari.UndefinedNoneOr[t.Any] = hikari.UNDEFINED,
*,
flags: t.Union[int, hikari.MessageFlag, hikari.UndefinedType] = hikari.UNDEFINED,
tts: hikari.UndefinedOr[bool] = hikari.UNDEFINED,
component: hikari.UndefinedOr[hikari.api.ComponentBuilder] = hikari.UNDEFINED,
components: hikari.UndefinedOr[t.Sequence[hikari.api.ComponentBuilder]] = hikari.UNDEFINED,
attachment: hikari.UndefinedNoneOr[hikari.Resourceish] = hikari.UNDEFINED,
attachments: hikari.UndefinedNoneOr[t.Sequence[hikari.Resourceish]] = hikari.UNDEFINED,
embed: hikari.UndefinedOr[hikari.Embed] = hikari.UNDEFINED,
embeds: hikari.UndefinedOr[t.Sequence[hikari.Embed]] = hikari.UNDEFINED,
mentions_everyone: hikari.UndefinedOr[bool] = hikari.UNDEFINED,
user_mentions: hikari.UndefinedOr[
t.Union[hikari.SnowflakeishSequence[hikari.PartialUser], bool]
] = hikari.UNDEFINED,
role_mentions: hikari.UndefinedOr[
t.Union[hikari.SnowflakeishSequence[hikari.PartialRole], bool]
] = hikari.UNDEFINED,
) -> InteractionResponse:
"""A short-hand method to edit the last message belonging to this interaction.
In the case of modals, this will be the component's message that triggered the modal.
Args:
content:
The content of the message. Anything passed here will be cast to str.
tts:
If the message should be tts or not.
attachment:
An attachment to add to this message.
attachments:
A sequence of attachments to add to this message.
component:
A component to add to this message.
components:
A sequence of components to add to this message.
embed:
An embed to add to this message.
embeds:
A sequence of embeds to add to this message.
mentions_everyone:
If True, mentioning @everyone will be allowed.
user_mentions:
The set of allowed user mentions in this message. Set to True to allow all.
role_mentions:
The set of allowed role mentions in this message. Set to True to allow all.
flags:
Message flags that should be included with this message.
Returns:
InteractionResponse: A proxy object representing the response to the interaction.
"""
if self._issued_response:
message = await self.interaction.edit_initial_response(
content,
component=component,
components=components,
attachment=attachment,
attachments=attachments,
embed=embed,
embeds=embeds,
mentions_everyone=mentions_everyone,
user_mentions=user_mentions,
role_mentions=role_mentions,
)
return self._create_response(message)
else:
await self.interaction.create_initial_response(
hikari.ResponseType.MESSAGE_UPDATE,
content,
component=component,
components=components,
attachment=attachment,
attachments=attachments,
tts=tts,
embed=embed,
embeds=embeds,
mentions_everyone=mentions_everyone,
user_mentions=user_mentions,
role_mentions=role_mentions,
flags=flags,
)
return self._create_response()
[docs]
async def defer(
self,
response_type: t.Literal[
hikari.ResponseType.DEFERRED_MESSAGE_CREATE, hikari.ResponseType.DEFERRED_MESSAGE_UPDATE
] = hikari.ResponseType.DEFERRED_MESSAGE_CREATE,
*,
flags: hikari.UndefinedOr[t.Union[int, hikari.MessageFlag]] = hikari.UNDEFINED,
) -> None:
"""Short-hand method to defer an interaction response. Raises RuntimeError if the interaction was already responded to.
Parameters
----------
response_type : t.Literal[hikari.ResponseType.DEFERRED_MESSAGE_CREATE, hikari.ResponseType.DEFERRED_MESSAGE_UPDATE], optional
The response-type of this defer action. Defaults to DEFERRED_MESSAGE_UPDATE.
flags : t.Union[int, hikari.MessageFlag, None], optional
Message flags that should be included with this defer request, by default None
Raises
------
RuntimeError
The interaction was already responded to.
ValueError
response_type was not a deferred response type.
"""
if response_type not in [
hikari.ResponseType.DEFERRED_MESSAGE_CREATE,
hikari.ResponseType.DEFERRED_MESSAGE_UPDATE,
]:
raise ValueError(
"Parameter response_type must be ResponseType.DEFERRED_MESSAGE_CREATE or ResponseType.DEFERRED_MESSAGE_UPDATE."
)
if self._issued_response:
raise RuntimeError("Interaction was already responded to.")
await self.interaction.create_initial_response(response_type, flags=flags)
self._issued_response = True
# MIT License
#
# Copyright (c) 2022-present HyperGH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.