diff --git a/interactions/client/client.py b/interactions/client/client.py index bb55b9f75..6c261ed34 100644 --- a/interactions/client/client.py +++ b/interactions/client/client.py @@ -544,6 +544,7 @@ def get_guild_websocket(self, id: "Snowflake_Type") -> GatewayClient: def _sanity_check(self) -> None: """Checks for possible and common errors in the bot's configuration.""" self.logger.debug("Running client sanity checks...") + contexts = { self.interaction_context: InteractionContext, self.component_context: ComponentContext, @@ -911,6 +912,11 @@ async def login(self, token: str | None = None) -> None: # so im gathering commands here self._gather_callbacks() + if any(v for v in constants.CLIENT_FEATURE_FLAGS.values()): + # list all enabled flags + enabled_flags = [k for k, v in constants.CLIENT_FEATURE_FLAGS.items() if v] + self.logger.info(f"Enabled feature flags: {', '.join(enabled_flags)}") + self.logger.debug("Attempting to login") me = await self.http.login(self.token) self._user = ClientUser.from_dict(me, self) @@ -1105,7 +1111,7 @@ async def wait_for_component( dict, ] ] = None, - check: Optional[Callable] = None, + check: Absent[Optional[Union[Callable[..., bool], Callable[..., Awaitable[bool]]]]] = None, timeout: Optional[float] = None, ) -> "events.Component": """ @@ -1136,7 +1142,7 @@ async def wait_for_component( if custom_ids and not all(isinstance(x, str) for x in custom_ids): custom_ids = [str(i) for i in custom_ids] - def _check(event: events.Component) -> bool: + async def _check(event: events.Component) -> bool: ctx: ComponentContext = event.ctx # if custom_ids is empty or there is a match wanted_message = not message_ids or ctx.message.id in ( @@ -1144,6 +1150,8 @@ def _check(event: events.Component) -> bool: ) wanted_component = not custom_ids or ctx.custom_id in custom_ids if wanted_message and wanted_component: + if asyncio.iscoroutinefunction(check): + return bool(check is None or await check(event)) return bool(check is None or check(event)) return False @@ -1151,7 +1159,9 @@ def _check(event: events.Component) -> bool: def command(self, *args, **kwargs) -> Callable: """A decorator that registers a command. Aliases `interactions.slash_command`""" - raise NotImplementedError # TODO: implement + raise NotImplementedError( + "Use interactions.slash_command instead. Please consult the v4 -> v5 migration guide https://interactions-py.github.io/interactions.py/Guides/98%20Migration%20from%204.X/" + ) def listen(self, event_name: Absent[str] = MISSING) -> Callable[[AsyncCallable], Listener]: """ diff --git a/interactions/client/const.py b/interactions/client/const.py index db562f5f7..1d0ced829 100644 --- a/interactions/client/const.py +++ b/interactions/client/const.py @@ -32,6 +32,9 @@ T TypeVar: A type variable used for generic typing. Absent Union[T, Missing]: A type hint for a value that may be MISSING. + CLIENT_FEATURE_FLAGS dict: A dict of feature flags that can be enabled or disabled for the client. + has_feature_flag Callable[[str], bool]: A function that returns whether a feature flag is enabled. + """ import inspect import logging @@ -78,6 +81,8 @@ "LIB_PATH", "RECOVERABLE_WEBSOCKET_CLOSE_CODES", "NON_RESUMABLE_WEBSOCKET_CLOSE_CODES", + "CLIENT_FEATURE_FLAGS", + "has_client_feature", ) _ver_info = sys.version_info @@ -195,6 +200,18 @@ class MentionPrefix(Sentinel): }, ) +CLIENT_FEATURE_FLAGS = { + "FOLLOWUP_INTERACTIONS_FOR_IMAGES": False, # Experimental fix to bypass Discord's broken image proxy +} + + +def has_client_feature(feature: str) -> bool: + """Checks if a feature is enabled for the client.""" + if feature.upper() not in CLIENT_FEATURE_FLAGS: + get_logger().warning(f"Unknown feature {feature!r} - Known features: {list(CLIENT_FEATURE_FLAGS)}") + return False + return CLIENT_FEATURE_FLAGS[feature.upper()] + GUILD_WELCOME_MESSAGES = ( "{0} joined the party.", diff --git a/interactions/models/internal/context.py b/interactions/models/internal/context.py index 0ec20c5a8..9640a5f8d 100644 --- a/interactions/models/internal/context.py +++ b/interactions/models/internal/context.py @@ -6,6 +6,8 @@ import discord_typings from aiohttp import FormData + +from interactions.client import const from interactions.client.const import get_logger, MISSING from interactions.models.discord.components import BaseComponent from interactions.models.discord.file import UPLOADABLE_TYPE @@ -401,6 +403,14 @@ async def defer(self, *, ephemeral: bool = False) -> None: async def _send_http_request( self, message_payload: dict, files: typing.Iterable["UPLOADABLE_TYPE"] | None = None ) -> dict: + if const.has_client_feature("FOLLOWUP_INTERACTIONS_FOR_IMAGES") and not self.deferred: + # experimental bypass for discords broken image proxy + if embeds := message_payload.get("embeds", {}): + if any(e.get("image") for e in embeds) or any(e.get("thumbnail") for e in embeds): + if MessageFlags.EPHEMERAL in message_payload.get("flags", MessageFlags.NONE): + self.ephemeral = True + await self.defer(ephemeral=self.ephemeral) + if self.responded: message_data = await self.client.http.post_followup( message_payload, self.client.app.id, self.token, files=files @@ -409,9 +419,14 @@ async def _send_http_request( if isinstance(message_payload, FormData) and not self.deferred: await self.defer(ephemeral=self.ephemeral) if self.deferred: - message_data = await self.client.http.edit_interaction_message( - message_payload, self.client.app.id, self.token, files=files - ) + if const.has_client_feature("FOLLOWUP_INTERACTIONS_FOR_IMAGES"): + message_data = await self.client.http.post_followup( + message_payload, self.client.app.id, self.token, files=files + ) + else: + message_data = await self.client.http.edit_interaction_message( + message_payload, self.client.app.id, self.token, files=files + ) else: payload = { "type": CallbackType.CHANNEL_MESSAGE_WITH_SOURCE, diff --git a/pyproject.toml b/pyproject.toml index 910d2cb5e..93bc9250b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "interactions.py" -version = "5.4.0" +version = "5.5.0" description = "Easy, simple, scalable and modular: a Python API wrapper for interactions." authors = [ "LordOfPolls ",