Skip to content

Commit cf0e203

Browse files
IcebluewolfPaillat-dev
authored andcommitted
feat: Implement with_response For Interaction Callbacks (Pycord-Development#2711)
Signed-off-by: Ice Wolfy <[email protected]> Signed-off-by: Lala Sabathil <[email protected]> Signed-off-by: Paillat <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Lala Sabathil <[email protected]> Co-authored-by: Paillat <[email protected]> (cherry picked from commit 3fd5d70) Signed-off-by: Paillat-dev <[email protected]>
1 parent 22461a2 commit cf0e203

File tree

5 files changed

+179
-44
lines changed

5 files changed

+179
-44
lines changed

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,27 @@ possible (see our [Version Guarantees] for more info).
1010

1111
These changes are available on the `master` branch, but have not yet been released.
1212

13+
### Added
14+
15+
- Implemented `with_response` for interaction callbacks, adding
16+
`Interaction.callback.is_loading()` and `Interaction.callback.is_ephemeral()`.
17+
([#2711](https://github.com/Pycord-Development/pycord/pull/2711))
18+
- Added `RawMessageUpdateEvent.new_message` - message update events now contain full
19+
message objects ([#2780](https://github.com/Pycord-Development/pycord/pull/2780))
20+
21+
### Changed
22+
23+
### Fixed
24+
25+
- Manage silence for new SSRC with existing user_id.
26+
([#2808](https://github.com/Pycord-Development/pycord/pull/2808))
27+
- Unbound `raw` reference in `parse_message_update` causing errors on message updates.
28+
([#2905](https://github.com/Pycord-Development/pycord/pull/2905))
29+
30+
### Removed
31+
32+
## [2.7.0rc1] - 2025-08-30
33+
1334
⚠️ **This version removes support for Python 3.8.** ⚠️
1435

1536
### Added

discord/interactions.py

Lines changed: 127 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"MessageInteraction",
6363
"InteractionMetadata",
6464
"AuthorizingIntegrationOwners",
65+
"InteractionCallback",
6566
)
6667

6768
if TYPE_CHECKING:
@@ -84,7 +85,8 @@
8485
from .state import ConnectionState
8586
from .threads import Thread
8687
from .types.interactions import Interaction as InteractionPayload
87-
from .types.interactions import InteractionData
88+
from .types.interactions import InteractionCallback as InteractionCallbackPayload
89+
from .types.interactions import InteractionCallbackResponse, InteractionData
8890
from .types.interactions import InteractionMetadata as InteractionMetadataPayload
8991
from .types.interactions import MessageInteraction as MessageInteractionPayload
9092
from .ui.modal import Modal
@@ -154,6 +156,11 @@ class Interaction:
154156
The context in which this command was executed.
155157
156158
.. versionadded:: 2.6
159+
callback: Optional[:class:`InteractionCallback`]
160+
The callback of the interaction. Contains information about the status of the interaction response.
161+
Will be `None` until the interaction is responded to.
162+
163+
.. versionadded:: 2.7
157164
command: Optional[:class:`ApplicationCommand`]
158165
The command that this interaction belongs to.
159166
@@ -190,6 +197,7 @@ class Interaction:
190197
"entitlements",
191198
"context",
192199
"authorizing_integration_owners",
200+
"callback",
193201
"command",
194202
"view",
195203
"modal",
@@ -213,6 +221,7 @@ def __init__(self, *, data: InteractionPayload, state: ConnectionState):
213221
self._state: ConnectionState = state
214222
self._session: ClientSession = state.http._HTTPClient__session
215223
self._original_response: InteractionMessage | None = None
224+
self.callback: InteractionCallback | None = None
216225
self._from_data(data)
217226

218227
def _from_data(self, data: InteractionPayload):
@@ -443,7 +452,9 @@ async def original_response(self) -> InteractionMessage:
443452
# TODO: fix later to not raise?
444453
channel = self.channel
445454
if channel is None:
446-
raise ClientException("Channel for message could not be resolved")
455+
raise ClientException(
456+
"Channel for message could not be resolved. Please open a issue on GitHub if you encounter this error."
457+
)
447458

448459
adapter = async_context.get()
449460
http = self._state.http
@@ -828,18 +839,21 @@ async def defer(self, *, ephemeral: bool = False, invisible: bool = True) -> Non
828839
if defer_type:
829840
adapter = async_context.get()
830841
http = parent._state.http
831-
await self._locked_response(
832-
adapter.create_interaction_response(
833-
parent.id,
834-
parent.token,
835-
session=parent._session,
836-
type=defer_type,
837-
data=data,
838-
proxy=http.proxy,
839-
proxy_auth=http.proxy_auth,
842+
callback_response: InteractionCallbackResponse = (
843+
await self._locked_response(
844+
adapter.create_interaction_response(
845+
parent.id,
846+
parent.token,
847+
session=parent._session,
848+
type=defer_type,
849+
data=data,
850+
proxy=http.proxy,
851+
proxy_auth=http.proxy_auth,
852+
)
840853
)
841854
)
842855
self._responded = True
856+
await self._process_callback_response(callback_response)
843857

844858
async def pong(self) -> None:
845859
"""|coro|
@@ -862,17 +876,36 @@ async def pong(self) -> None:
862876
if parent.type is InteractionType.ping:
863877
adapter = async_context.get()
864878
http = parent._state.http
865-
await self._locked_response(
866-
adapter.create_interaction_response(
867-
parent.id,
868-
parent.token,
869-
session=parent._session,
870-
proxy=http.proxy,
871-
proxy_auth=http.proxy_auth,
872-
type=InteractionResponseType.pong.value,
879+
callback_response: InteractionCallbackResponse = (
880+
await self._locked_response(
881+
adapter.create_interaction_response(
882+
parent.id,
883+
parent.token,
884+
session=parent._session,
885+
proxy=http.proxy,
886+
proxy_auth=http.proxy_auth,
887+
type=InteractionResponseType.pong.value,
888+
)
873889
)
874890
)
875891
self._responded = True
892+
await self._process_callback_response(callback_response)
893+
894+
async def _process_callback_response(
895+
self, callback_response: InteractionCallbackResponse
896+
):
897+
if callback_response.get("resource", {}).get("message"):
898+
# TODO: fix later to not raise?
899+
channel = self._parent.channel
900+
if channel is None:
901+
raise ClientException(
902+
"Channel for message could not be resolved. Please open a issue on GitHub if you encounter this error."
903+
)
904+
state = _InteractionMessageState(self._parent, self._parent._state)
905+
message = InteractionMessage(state=state, channel=channel, data=callback_response["resource"]["message"]) # type: ignore
906+
self._parent._original_response = message
907+
908+
self._parent.callback = InteractionCallback(callback_response["interaction"])
876909

877910
async def send_message(
878911
self,
@@ -1008,16 +1041,18 @@ async def send_message(
10081041
adapter = async_context.get()
10091042
http = parent._state.http
10101043
try:
1011-
await self._locked_response(
1012-
adapter.create_interaction_response(
1013-
parent.id,
1014-
parent.token,
1015-
session=parent._session,
1016-
type=InteractionResponseType.channel_message.value,
1017-
proxy=http.proxy,
1018-
proxy_auth=http.proxy_auth,
1019-
data=payload,
1020-
files=files,
1044+
callback_response: InteractionCallbackResponse = (
1045+
await self._locked_response(
1046+
adapter.create_interaction_response(
1047+
parent.id,
1048+
parent.token,
1049+
session=parent._session,
1050+
type=InteractionResponseType.channel_message.value,
1051+
proxy=http.proxy,
1052+
proxy_auth=http.proxy_auth,
1053+
data=payload,
1054+
files=files,
1055+
)
10211056
)
10221057
)
10231058
finally:
@@ -1034,6 +1069,7 @@ async def send_message(
10341069
self._parent._state.store_view(view)
10351070

10361071
self._responded = True
1072+
await self._process_callback_response(callback_response)
10371073
if delete_after is not None:
10381074
await self._parent.delete_original_response(delay=delete_after)
10391075
return self._parent
@@ -1165,16 +1201,18 @@ async def edit_message(
11651201
adapter = async_context.get()
11661202
http = parent._state.http
11671203
try:
1168-
await self._locked_response(
1169-
adapter.create_interaction_response(
1170-
parent.id,
1171-
parent.token,
1172-
session=parent._session,
1173-
type=InteractionResponseType.message_update.value,
1174-
proxy=http.proxy,
1175-
proxy_auth=http.proxy_auth,
1176-
data=payload,
1177-
files=files,
1204+
callback_response: InteractionCallbackResponse = (
1205+
await self._locked_response(
1206+
adapter.create_interaction_response(
1207+
parent.id,
1208+
parent.token,
1209+
session=parent._session,
1210+
type=InteractionResponseType.message_update.value,
1211+
proxy=http.proxy,
1212+
proxy_auth=http.proxy_auth,
1213+
data=payload,
1214+
files=files,
1215+
)
11781216
)
11791217
)
11801218
finally:
@@ -1187,6 +1225,7 @@ async def edit_message(
11871225
state.store_view(view, message_id)
11881226

11891227
self._responded = True
1228+
await self._process_callback_response(callback_response)
11901229
if delete_after is not None:
11911230
await self._parent.delete_original_response(delay=delete_after)
11921231

@@ -1222,7 +1261,7 @@ async def send_autocomplete_result(
12221261

12231262
adapter = async_context.get()
12241263
http = parent._state.http
1225-
await self._locked_response(
1264+
callback_response: InteractionCallbackResponse = await self._locked_response(
12261265
adapter.create_interaction_response(
12271266
parent.id,
12281267
parent.token,
@@ -1235,6 +1274,7 @@ async def send_autocomplete_result(
12351274
)
12361275

12371276
self._responded = True
1277+
await self._process_callback_response(callback_response)
12381278

12391279
async def send_modal(self, modal: Modal) -> Interaction:
12401280
"""|coro|
@@ -1261,7 +1301,7 @@ async def send_modal(self, modal: Modal) -> Interaction:
12611301
payload = modal.to_dict()
12621302
adapter = async_context.get()
12631303
http = parent._state.http
1264-
await self._locked_response(
1304+
callback_response: InteractionCallbackResponse = await self._locked_response(
12651305
adapter.create_interaction_response(
12661306
parent.id,
12671307
parent.token,
@@ -1273,6 +1313,7 @@ async def send_modal(self, modal: Modal) -> Interaction:
12731313
)
12741314
)
12751315
self._responded = True
1316+
await self._process_callback_response(callback_response)
12761317
self._parent._state.store_modal(modal, self._parent.user.id)
12771318
return self._parent
12781319

@@ -1300,7 +1341,7 @@ async def premium_required(self) -> Interaction:
13001341

13011342
adapter = async_context.get()
13021343
http = parent._state.http
1303-
await self._locked_response(
1344+
callback_response: InteractionCallbackResponse = await self._locked_response(
13041345
adapter.create_interaction_response(
13051346
parent.id,
13061347
parent.token,
@@ -1311,9 +1352,10 @@ async def premium_required(self) -> Interaction:
13111352
)
13121353
)
13131354
self._responded = True
1355+
await self._process_callback_response(callback_response)
13141356
return self._parent
13151357

1316-
async def _locked_response(self, coro: Coroutine[Any, Any, Any]) -> None:
1358+
async def _locked_response(self, coro: Coroutine[Any, Any, Any]) -> Any:
13171359
"""|coro|
13181360
13191361
Wraps a response and makes sure that it's locked while executing.
@@ -1323,16 +1365,24 @@ async def _locked_response(self, coro: Coroutine[Any, Any, Any]) -> None:
13231365
coro: Coroutine[Any]
13241366
The coroutine to wrap.
13251367
1368+
Returns
1369+
-------
1370+
Any
1371+
The result of the coroutine.
1372+
13261373
Raises
13271374
------
13281375
InteractionResponded
13291376
This interaction has already been responded to before.
1377+
1378+
.. versionchanged:: 2.7
1379+
Return the result of the coroutine
13301380
"""
13311381
async with self._response_lock:
13321382
if self.is_done():
13331383
coro.close() # cleanup un-awaited coroutine
13341384
raise InteractionResponded(self._parent)
1335-
await coro
1385+
return await coro
13361386

13371387

13381388
class _InteractionMessageState:
@@ -1646,3 +1696,36 @@ def guild(self) -> Guild | None:
16461696
if not self.guild_id:
16471697
return None
16481698
return self._state._get_guild(self.guild_id)
1699+
1700+
1701+
class InteractionCallback:
1702+
"""Information about the status of the interaction response.
1703+
1704+
.. versionadded:: 2.7
1705+
"""
1706+
1707+
def __init__(self, data: InteractionCallbackPayload):
1708+
self._response_message_loading: bool = data.get(
1709+
"response_message_loading", False
1710+
)
1711+
self._response_message_ephemeral: bool = data.get(
1712+
"response_message_ephemeral", False
1713+
)
1714+
1715+
def __repr__(self):
1716+
return (
1717+
f"<InteractionCallback "
1718+
f"_response_message_loading={self._response_message_loading} "
1719+
f"_response_message_ephemeral={self._response_message_ephemeral}>"
1720+
)
1721+
1722+
def is_loading(self) -> bool:
1723+
"""Indicates whether the response message is in a loading state."""
1724+
return self._response_message_loading
1725+
1726+
def is_ephemeral(self) -> bool:
1727+
"""Indicates whether the response message is ephemeral.
1728+
1729+
This might be useful for determining if the message was forced to be ephemeral.
1730+
"""
1731+
return self._response_message_ephemeral

discord/types/interactions.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,3 +261,24 @@ class EditApplicationCommand(TypedDict):
261261
_StringApplicationIntegrationType = Literal["0", "1"]
262262

263263
AuthorizingIntegrationOwners = Dict[_StringApplicationIntegrationType, Snowflake]
264+
265+
266+
class InteractionCallbackResponse(TypedDict):
267+
interaction: InteractionCallback
268+
resource: NotRequired[InteractionCallbackResource]
269+
270+
271+
class InteractionCallback(TypedDict):
272+
id: Snowflake
273+
type: InteractionType
274+
activity_instance_id: NotRequired[str]
275+
response_message_id: NotRequired[Snowflake]
276+
response_message_loading: NotRequired[bool]
277+
response_message_ephemeral: NotRequired[bool]
278+
279+
280+
class InteractionCallbackResource(TypedDict):
281+
type: InteractionResponseType
282+
# This is not fully typed as activities are out of scope
283+
activity_instance: NotRequired[dict]
284+
message: NotRequired[Message]

discord/webhook/async_.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,13 +530,18 @@ def create_interaction_response(
530530
webhook_token=token,
531531
)
532532

533+
params: dict[str, Any] = {
534+
"with_response": "true",
535+
}
536+
533537
return self.request(
534538
route,
535539
session=session,
536540
proxy=proxy,
537541
proxy_auth=proxy_auth,
538542
files=files,
539543
multipart=form,
544+
params=params,
540545
)
541546

542547
def get_original_interaction_response(

0 commit comments

Comments
 (0)