Remove old workaround to /callback not having a return type

The old code needed a workaround using interaction_id to differentiate
between multiple instances being reused since they would all go into
the `None` key. Since /callback now returns a proper message_id this
could be used as a key instead of None. From testing, it seems this is
true for both edit_message and send_message responses.
This commit is contained in:
Rapptz
2026-03-02 12:29:02 -05:00
parent 616137875b
commit b01de35fa2
4 changed files with 23 additions and 59 deletions

View File

@@ -615,7 +615,7 @@ class Interaction(Generic[ClientT]):
state = _InteractionMessageState(self, self._state) state = _InteractionMessageState(self, self._state)
message = InteractionMessage(state=state, channel=self.channel, data=data) # type: ignore message = InteractionMessage(state=state, channel=self.channel, data=data) # type: ignore
if view and not view.is_finished() and view.is_dispatchable(): if view and not view.is_finished() and view.is_dispatchable():
self._state.store_view(view, message.id, interaction_id=self.id) self._state.store_view(view, message.id)
return message return message
async def delete_original_response(self) -> None: async def delete_original_response(self) -> None:
@@ -1082,7 +1082,7 @@ class InteractionResponse(Generic[ClientT]):
) )
http = parent._state.http http = parent._state.http
response = await adapter.create_interaction_response( data = await adapter.create_interaction_response(
parent.id, parent.id,
parent.token, parent.token,
session=parent._session, session=parent._session,
@@ -1090,17 +1090,19 @@ class InteractionResponse(Generic[ClientT]):
proxy_auth=http.proxy_auth, proxy_auth=http.proxy_auth,
params=params, params=params,
) )
self._response_type = InteractionResponseType.channel_message
response = InteractionCallbackResponse(
data=data,
parent=self._parent,
state=self._parent._state,
type=self._response_type,
)
if view is not MISSING and not view.is_finished(): if view is not MISSING and not view.is_finished():
if ephemeral and view.timeout is None: if ephemeral and view.timeout is None:
view.timeout = 15 * 60.0 view.timeout = 15 * 60.0
# If the interaction type isn't an application command then there's no way self._parent._state.store_view(view, response.message_id)
# to obtain this interaction_id again, so just default to None
entity_id = parent.id if parent.type is InteractionType.application_command else None
self._parent._state.store_view(view, entity_id)
self._response_type = InteractionResponseType.channel_message
if delete_after is not None: if delete_after is not None:
@@ -1113,12 +1115,7 @@ class InteractionResponse(Generic[ClientT]):
asyncio.create_task(inner_call()) asyncio.create_task(inner_call())
return InteractionCallbackResponse( return response
data=response,
parent=self._parent,
state=self._parent._state,
type=self._response_type,
)
async def edit_message( async def edit_message(
self, self,
@@ -1205,12 +1202,8 @@ class InteractionResponse(Generic[ClientT]):
state = parent._state state = parent._state
if msg is not None: if msg is not None:
message_id = msg.id message_id = msg.id
# If this was invoked via an application command then we can use its original interaction ID
# Since this is used as a cache key for view updates
original_interaction_id = msg.interaction_metadata.id if msg.interaction_metadata is not None else None
else: else:
message_id = None message_id = None
original_interaction_id = None
if parent.type not in (InteractionType.component, InteractionType.modal_submit): if parent.type not in (InteractionType.component, InteractionType.modal_submit):
return return
@@ -1238,7 +1231,7 @@ class InteractionResponse(Generic[ClientT]):
) )
http = parent._state.http http = parent._state.http
response = await adapter.create_interaction_response( data = await adapter.create_interaction_response(
parent.id, parent.id,
parent.token, parent.token,
session=parent._session, session=parent._session,
@@ -1246,11 +1239,16 @@ class InteractionResponse(Generic[ClientT]):
proxy_auth=http.proxy_auth, proxy_auth=http.proxy_auth,
params=params, params=params,
) )
self._response_type = InteractionResponseType.message_update
response = InteractionCallbackResponse(
data=data,
parent=self._parent,
state=self._parent._state,
type=self._response_type,
)
if view and not view.is_finished() and view.is_dispatchable(): if view and not view.is_finished() and view.is_dispatchable():
state.store_view(view, message_id, interaction_id=original_interaction_id) state.store_view(view, message_id or response.message_id)
self._response_type = InteractionResponseType.message_update
if delete_after is not None: if delete_after is not None:
@@ -1263,12 +1261,7 @@ class InteractionResponse(Generic[ClientT]):
asyncio.create_task(inner_call()) asyncio.create_task(inner_call())
return InteractionCallbackResponse( return response
data=response,
parent=self._parent,
state=self._parent._state,
type=self._response_type,
)
async def send_modal(self, modal: Modal, /) -> InteractionCallbackResponse[ClientT]: async def send_modal(self, modal: Modal, /) -> InteractionCallbackResponse[ClientT]:
"""|coro| """|coro|

View File

@@ -1415,10 +1415,6 @@ class PartialMessage(Hashable):
message = Message(state=self._state, channel=self.channel, data=data) message = Message(state=self._state, channel=self.channel, data=data)
if view and not view.is_finished() and view.is_dispatchable(): if view and not view.is_finished() and view.is_dispatchable():
interaction: Optional[MessageInteractionMetadata] = getattr(self, 'interaction_metadata', None)
if interaction is not None:
self._state.store_view(view, self.id, interaction_id=interaction.id)
else:
self._state.store_view(view, self.id) self._state.store_view(view, self.id)
if delete_after is not None: if delete_after is not None:

View File

@@ -412,9 +412,7 @@ class ConnectionState(Generic[ClientT]):
self._stickers[sticker_id] = sticker = GuildSticker(state=self, data=data) self._stickers[sticker_id] = sticker = GuildSticker(state=self, data=data)
return sticker return sticker
def store_view(self, view: BaseView, message_id: Optional[int] = None, interaction_id: Optional[int] = None) -> None: def store_view(self, view: BaseView, message_id: Optional[int] = None) -> None:
if interaction_id is not None:
self._view_store.remove_interaction_mapping(interaction_id)
self._view_store.add_view(view, message_id) self._view_store.add_view(view, message_id)
def prevent_view_updates_for(self, message_id: int) -> Optional[BaseView]: def prevent_view_updates_for(self, message_id: int) -> Optional[BaseView]:

View File

@@ -1053,15 +1053,12 @@ class ViewStore:
def dispatch_view(self, component_type: int, custom_id: str, interaction: Interaction[ClientT]) -> None: def dispatch_view(self, component_type: int, custom_id: str, interaction: Interaction[ClientT]) -> None:
self.dispatch_dynamic_items(component_type, custom_id, interaction) self.dispatch_dynamic_items(component_type, custom_id, interaction)
interaction_id: Optional[int] = None
message_id: Optional[int] = None message_id: Optional[int] = None
# Realistically, in a component based interaction the Interaction.message will never be None # Realistically, in a component based interaction the Interaction.message will never be None
# However, this guard is just in case Discord screws up somehow # However, this guard is just in case Discord screws up somehow
msg = interaction.message msg = interaction.message
if msg is not None: if msg is not None:
message_id = msg.id message_id = msg.id
if msg.interaction_metadata:
interaction_id = msg.interaction_metadata.id
key = (component_type, custom_id) key = (component_type, custom_id)
@@ -1070,21 +1067,6 @@ class ViewStore:
if message_id is not None: if message_id is not None:
item = self._views.get(message_id, {}).get(key) item = self._views.get(message_id, {}).get(key)
if item is None and interaction_id is not None:
try:
items = self._views.pop(interaction_id)
except KeyError:
item = None
else:
item = items.get(key)
# If we actually got the items, then these keys should probably be moved
# to the proper message_id instead of the interaction_id as they are now.
# An interaction_id is only used as a temporary stop gap for
# InteractionResponse.send_message so multiple view instances do not
# override each other.
# NOTE: Fix this mess if /callback endpoint ever gets proper return types
self._views.setdefault(message_id, {}).update(items)
if item is None: if item is None:
# Fallback to None message_id searches in case a persistent view # Fallback to None message_id searches in case a persistent view
# was added without an associated message_id # was added without an associated message_id
@@ -1116,11 +1098,6 @@ class ViewStore:
self.add_task(modal._dispatch_submit(interaction, components, resolved)) self.add_task(modal._dispatch_submit(interaction, components, resolved))
def remove_interaction_mapping(self, interaction_id: int) -> None:
# This is called before re-adding the view
self._views.pop(interaction_id, None)
self._synced_message_views.pop(interaction_id, None)
def is_message_tracked(self, message_id: int) -> bool: def is_message_tracked(self, message_id: int) -> bool:
return message_id in self._synced_message_views return message_id in self._synced_message_views