Repository: hikari-py/hikari
Pull request #1116 (2022-03-30 05:55:17)

Add animated guild banner support
### Summary
<!-- Small summary of the merge request -->

### Checklist
<!-- Make sure to tick all the following boxes by putting an `x` in between (like this `[x]`) -->
- [ ] I have run `nox` and all the pipelines have passed.
- [ ] I have made unittests according to the code I have added/modified/deleted.

### Related issues
<!--
To mention an issue use `#issue-id` and to mention a merge request use `!merge-request-id`
To close/fix an issue use `Close #issue-id` or `Fix #issue-id` (depending on the merge request)
-->



Patch:
diff --git a/changes/1016.feature.md b/changes/1016.feature.md
new file mode 100644
index 00000000..f8168d8d
--- /dev/null
+++ b/changes/1016.feature.md
@@ -0,0 +1,1 @@
+Add `create` method to `CommandBuilder`.
diff --git a/changes/1048.feature.md b/changes/1048.feature.md
new file mode 100644
index 00000000..d6b99b20
--- /dev/null
+++ b/changes/1048.feature.md
@@ -0,0 +1,1 @@
+Support for attachments in REST-based interaction responses.
diff --git a/changes/1116.feature.md b/changes/1116.feature.md
new file mode 100644
index 00000000..edf0dd6b
--- /dev/null
+++ b/changes/1116.feature.md
@@ -0,0 +1,1 @@
+Animated guild banner support.
diff --git a/hikari/api/interaction_server.py b/hikari/api/interaction_server.py
index f206a4a1..56a3e455 100644
--- a/hikari/api/interaction_server.py
+++ b/hikari/api/interaction_server.py
@@ -29,6 +29,7 @@
 import typing
 
 if typing.TYPE_CHECKING:
+    from hikari import files as files_
     from hikari.api import special_endpoints
     from hikari.interactions import base_interactions
     from hikari.interactions import command_interactions
@@ -68,25 +69,24 @@ class Response(typing.Protocol):
     __slots__: typing.Sequence[str] = ()
 
     @property
-    def headers(self) -> typing.Optional[typing.Mapping[str, str]]:
-        """Headers that should be added to the response if applicable.
+    def content_type(self) -> typing.Optional[str]:
+        """Content type of the response's payload, if applicable."""
+        raise NotImplementedError
 
-        Returns
-        -------
-        typing.Optional[typing.Mapping[builtins.str, builtins.str]]
-            A mapping of string header names to string header values that should
-            be included in the response if applicable else `builtins.None`.
-        """
+    @property
+    def files(self) -> typing.Sequence[files_.Resource[files_.AsyncReader]]:
+        """Up to 10 files that should be included alongside a JSON response."""
+        raise NotImplementedError
+
+    @property
+    def headers(self) -> typing.Optional[typing.MutableMapping[str, str]]:
+        """Headers that should be added to the response if applicable."""
         raise NotImplementedError
 
     @property
     def payload(self) -> typing.Optional[bytes]:
         """Payload to provide in the response.
 
-        !!! note
-            If this is not `builtins.None` then an appropriate `"Content-Type"`
-            header should be declared in `Response.headers`
-
         Returns
         -------
         typing.Optional[builtins.bytes]
diff --git a/hikari/api/rest.py b/hikari/api/rest.py
index 683c5353..9068d967 100644
--- a/hikari/api/rest.py
+++ b/hikari/api/rest.py
@@ -6941,7 +6941,7 @@ async def create_slash_command(
     async def create_context_menu_command(
         self,
         application: snowflakes.SnowflakeishOr[guilds.PartialApplication],
-        type: typing.Literal[commands.CommandType.USER, commands.CommandType.MESSAGE, 2, 3],
+        type: typing.Union[commands.CommandType, int],
         name: str,
         *,
         guild: undefined.UndefinedOr[snowflakes.SnowflakeishOr[guilds.PartialGuild]] = undefined.UNDEFINED,
@@ -6953,6 +6953,10 @@ async def create_context_menu_command(
         ----------
         application: hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication]
             Object or ID of the application to create a command for.
+        type : typing.Union[hikari.commands.CommandType, builtins.int]
+            The type of menu command to make.
+
+            Only USER and MESSAGE are valid here.
         name : builtins.str
             The command's name. This should match the regex `^[\w-]{1,32}$` in
             Unicode mode and be lowercase.
diff --git a/hikari/api/special_endpoints.py b/hikari/api/special_endpoints.py
index b75f1016..ce83ebac 100644
--- a/hikari/api/special_endpoints.py
+++ b/hikari/api/special_endpoints.py
@@ -63,6 +63,7 @@
     from hikari import users
     from hikari import voices
     from hikari.api import entity_factory as entity_factory_
+    from hikari.api import rest as rest_api
     from hikari.interactions import base_interactions
     from hikari.internal import data_binding
     from hikari.internal import time
@@ -566,7 +567,9 @@ def type(self) -> typing.Union[int, base_interactions.ResponseType]:
         """
 
     @abc.abstractmethod
-    def build(self, entity_factory: entity_factory_.EntityFactory, /) -> data_binding.JSONObject:
+    def build(
+        self, entity_factory: entity_factory_.EntityFactory, /
+    ) -> typing.Tuple[data_binding.JSONObject, typing.Sequence[files.Resource[files.AsyncReader]]]:
         """Build a JSON object from this builder.
 
         Parameters
@@ -576,8 +579,9 @@ def build(self, entity_factory: entity_factory_.EntityFactory, /) -> data_bindin
 
         Returns
         -------
-        hikari.internal.data_binding.JSONObject
-            The built json object representation of this builder.
+        typing.Tuple[hikari.internal.data_binding.JSONObject, typing.Sequence[files.Resource[Files.AsyncReader]]
+            A tuple of the built json object representation of this builder and
+            a sequence of up to 10 files to send with the response.
         """
 
 
@@ -680,19 +684,18 @@ def type(self) -> base_interactions.MessageResponseTypesT:
 
     @property
     @abc.abstractmethod
-    def components(self) -> typing.Sequence[ComponentBuilder]:
-        """Sequence of up to 5 component builders to send in this response."""
+    def attachments(self) -> undefined.UndefinedOr[typing.Sequence[files.Resourceish]]:
+        """Sequence of up to 10 attachments to send with the message."""
 
     @property
     @abc.abstractmethod
-    def embeds(self) -> typing.Sequence[embeds_.Embed]:
-        """Sequence of up to 10 of the embeds included in this response.
+    def components(self) -> undefined.UndefinedOr[typing.Sequence[ComponentBuilder]]:
+        """Sequence of up to 5 component builders to send in this response."""
 
-        Returns
-        -------
-        typing.Sequence[hikari.embeds.Embed]
-            A sequence of up to 10 of the embeds included in this response.
-        """
+    @property
+    @abc.abstractmethod
+    def embeds(self) -> undefined.UndefinedOr[typing.Sequence[embeds_.Embed]]:
+        """Sequence of up to 10 of the embeds included in this response."""
 
     # Settable fields
 
@@ -777,6 +780,21 @@ def user_mentions(
             mentions or `True` to allow all user mentions.
         """  # noqa: E501 - Line too long
 
+    @abc.abstractmethod
+    def add_attachment(self: _T, attachment: files.Resourceish, /) -> _T:
+        """Add an attachment to this response.
+
+        Parameters
+        ----------
+        attachment : hikari.files.Resourceish
+            The attachment to add.
+
+        Returns
+        -------
+        InteractionMessageBuilder
+            Object of this builder.
+        """
+
     @abc.abstractmethod
     def add_component(self: _T, component: ComponentBuilder, /) -> _T:
         """Add a component to this response.
@@ -1016,6 +1034,37 @@ def build(self, entity_factory: entity_factory_.EntityFactory, /) -> data_bindin
             The built json object representation of this builder.
         """
 
+    @abc.abstractmethod
+    async def create(
+        self,
+        rest: rest_api.RESTClient,
+        application: snowflakes.SnowflakeishOr[guilds.PartialApplication],
+        /,
+        *,
+        guild: undefined.UndefinedOr[snowflakes.SnowflakeishOr[guilds.PartialGuild]] = undefined.UNDEFINED,
+    ) -> commands.PartialCommand:
+        """Create this command through a REST call.
+
+        Parameters
+        ----------
+        rest : hikari.api.rest.RESTClient
+            The REST client to use to make this request.
+        application : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication]
+            The application to create this command for.
+
+        Other Parameters
+        ----------------
+        guild : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild]]
+            The guild to create this command for.
+
+            If left undefined then this command will be declared globally.
+
+        Returns
+        -------
+        hikari.commands.PartialCommand
+            The created command.
+        """
+
 
 class SlashCommandBuilder(CommandBuilder):
     """SlashCommandBuilder."""
@@ -1065,12 +1114,81 @@ def add_option(self: _T, option: commands.CommandOption) -> _T:
             Object of this command builder.
         """
 
+    @abc.abstractmethod
+    async def create(
+        self,
+        rest: rest_api.RESTClient,
+        application: snowflakes.SnowflakeishOr[guilds.PartialApplication],
+        /,
+        *,
+        guild: undefined.UndefinedOr[snowflakes.SnowflakeishOr[guilds.PartialGuild]] = undefined.UNDEFINED,
+    ) -> commands.SlashCommand:
+        """Create this command through a REST call.
+
+        This is a shorthand for calling `hikari.api.rest.RESTClient.create_slash_command`
+        with the builder's information.
+
+        Parameters
+        ----------
+        rest : hikari.api.rest.RESTClient
+            The REST client to use to make this request.
+        application : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication]
+            The application to create this command for.
+
+        Other Parameters
+        ----------------
+        guild : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild]]
+            The guild to create this command for.
+
+            If left undefined then this command will be declared globally.
+
+        Returns
+        -------
+        hikari.commands.SlashCommand
+            The created command.
+        """
+
 
 class ContextMenuCommandBuilder(CommandBuilder):
     """ContextMenuCommandBuilder."""
 
     __slots__: typing.Sequence[str] = ()
 
+    @abc.abstractmethod
+    async def create(
+        self,
+        rest: rest_api.RESTClient,
+        application: snowflakes.SnowflakeishOr[guilds.PartialApplication],
+        /,
+        *,
+        guild: undefined.UndefinedOr[snowflakes.SnowflakeishOr[guilds.PartialGuild]] = undefined.UNDEFINED,
+    ) -> commands.ContextMenuCommand:
+        """Create this command through a REST call.
+
+        This is a shorthand for calling
+        `hikari.api.rest.RESTClient.create_context_menu_command`
+        with the builder's information.
+
+        Parameters
+        ----------
+        rest : hikari.api.rest.RESTClient
+            The REST client to use to make this request.
+        application : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication]
+            The application to create this command for.
+
+        Other Parameters
+        ----------------
+        guild : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild]]
+            The guild to create this command for.
+
+            If left undefined then this command will be declared globally.
+
+        Returns
+        -------
+        hikari.commands.ContextMenuCommand
+            The created command.
+        """
+
 
 class ComponentBuilder(abc.ABC):
     """Base class for all component builder classes."""
diff --git a/hikari/guilds.py b/hikari/guilds.py
index 7af497ec..0155f078 100644
--- a/hikari/guilds.py
+++ b/hikari/guilds.py
@@ -2927,14 +2927,18 @@ def get_roles(self) -> typing.Mapping[snowflakes.Snowflake, Role]:
 
         return self.app.cache.get_roles_view_for_guild(self.id)
 
-    def make_banner_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optional[files.URL]:
+    def make_banner_url(self, *, ext: typing.Optional[str] = None, size: int = 4096) -> typing.Optional[files.URL]:
         """Generate the guild's banner image URL, if set.
 
         Parameters
         ----------
-        ext : builtins.str
-            The extension to use for this URL, defaults to `png`.
-            Supports `png`, `jpeg`, `jpg` and `webp`.
+        ext : typing.Optional[builtins.str]
+            The ext to use for this URL, defaults to `png` or `gif`.
+            Supports `png`, `jpeg`, `jpg`, `webp` and `gif` (when
+            animated).
+
+            If `builtins.None`, then the correct default extension is
+            determined based on whether the banner is animated or not.
         size : builtins.int
             The size to set for the URL, defaults to `4096`.
             Can be any power of two between 16 and 4096.
@@ -2952,6 +2956,13 @@ def make_banner_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optio
         if self.banner_hash is None:
             return None
 
+        if ext is None:
+            if self.banner_hash.startswith("a_"):
+                ext = "gif"
+
+            else:
+                ext = "png"
+
         return routes.CDN_GUILD_BANNER.compile_to_file(
             urls.CDN_URL,
             guild_id=self.id,
diff --git a/hikari/impl/interaction_server.py b/hikari/impl/interaction_server.py
index fb850cda..96036a09 100644
--- a/hikari/impl/interaction_server.py
+++ b/hikari/impl/interaction_server.py
@@ -30,6 +30,7 @@
 import threading
 import typing
 
+import aiohttp
 import aiohttp.web
 import aiohttp.web_runner
 
@@ -41,14 +42,17 @@
 from hikari.internal import data_binding
 
 if typing.TYPE_CHECKING:
+    import concurrent.futures
     import socket as socket_
     import ssl
 
+    import aiohttp.abc
     import aiohttp.typedefs
 
     # This is kept inline as pynacl is an optional dependency.
     from nacl import signing
 
+    from hikari import files as files_
     from hikari.api import entity_factory as entity_factory_api
     from hikari.api import rest as rest_api
     from hikari.interactions import command_interactions
@@ -81,6 +85,7 @@
 _X_SIGNATURE_TIMESTAMP_HEADER: typing.Final[str] = "X-Signature-Timestamp"
 _CONTENT_TYPE_KEY: typing.Final[str] = "Content-Type"
 _USER_AGENT_KEY: typing.Final[str] = "User-Agent"
+_APPLICATION_OCTET_STREAM: typing.Final[str] = "application/octet-stream"
 _JSON_CONTENT_TYPE: typing.Final[str] = "application/json"
 _JSON_TYPE_WITH_CHARSET: typing.Final[str] = f"{_JSON_CONTENT_TYPE}; charset={_UTF_8_CHARSET}"
 _TEXT_CONTENT_TYPE: typing.Final[str] = "text/plain"
@@ -88,7 +93,7 @@
 
 
 class _Response:
-    __slots__: typing.Sequence[str] = ("_headers", "_payload", "_status_code")
+    __slots__: typing.Sequence[str] = ("_content_type", "_files", "_payload", "_status_code")
 
     def __init__(
         self,
@@ -96,17 +101,27 @@ def __init__(
         payload: typing.Optional[bytes] = None,
         *,
         content_type: typing.Optional[str] = None,
+        files: typing.Sequence[files_.Resource[files_.AsyncReader]] = (),
     ) -> None:
-        self._headers = None
-        if payload or content_type:
-            self._headers = {_CONTENT_TYPE_KEY: content_type or _TEXT_TYPE_WITH_CHARSET}
+        if payload and not content_type:
+            content_type = _TEXT_TYPE_WITH_CHARSET
 
+        self._content_type = content_type
+        self._files = files
         self._payload = payload
         self._status_code = status_code
 
     @property
-    def headers(self) -> typing.Optional[typing.Mapping[str, str]]:
-        return self._headers
+    def content_type(self) -> typing.Optional[str]:
+        return self._content_type
+
+    @property
+    def files(self) -> typing.Sequence[files_.Resource[files_.AsyncReader]]:
+        return self._files
+
+    @property
+    def headers(self) -> typing.Optional[typing.MutableMapping[str, str]]:
+        return None
 
     @property
     def payload(self) -> typing.Optional[bytes]:
@@ -123,6 +138,27 @@ def status_code(self) -> int:
 )
 
 
+class _FilePayload(aiohttp.Payload):
+    _value: files_.Resource[files_.AsyncReader]
+
+    def __init__(
+        self,
+        value: files_.Resource[files_.AsyncReader],
+        content_type: str,
+        /,
+        *,
+        executor: typing.Optional[concurrent.futures.Executor] = None,
+        headers: typing.Optional[typing.Dict[str, str]] = None,
+    ) -> None:
+        super().__init__(value=value, headers=headers, content_type=content_type)
+        self._executor = executor
+
+    async def write(self, writer: aiohttp.abc.AbstractStreamWriter) -> None:
+        async with self._value.stream(executor=self._executor) as data:
+            async for chunk in data:
+                await writer.write(chunk)
+
+
 class InteractionServer(interaction_server.InteractionServer):
     """Standard implementation of `hikari.api.interaction_server.InteractionServer`.
 
@@ -150,6 +186,7 @@ class InteractionServer(interaction_server.InteractionServer):
         "_close_event",
         "_dumps",
         "_entity_factory",
+        "_executor",
         "_is_closing",
         "_listeners",
         "_loads",
@@ -164,6 +201,7 @@ def __init__(
         *,
         dumps: aiohttp.typedefs.JSONEncoder = data_binding.dump_json,
         entity_factory: entity_factory_api.EntityFactory,
+        executor: typing.Optional[concurrent.futures.Executor] = None,
         loads: aiohttp.typedefs.JSONDecoder = data_binding.load_json,
         rest_client: rest_api.RESTClient,
         public_key: typing.Optional[bytes] = None,
@@ -184,6 +222,7 @@ def __init__(
         self._close_event: typing.Optional[asyncio.Event] = None
         self._dumps = dumps
         self._entity_factory = entity_factory
+        self._executor = executor
         self._is_closing = False
         self._listeners: typing.Dict[typing.Type[base_interactions.PartialInteraction], typing.Any] = {}
         self._loads = loads
@@ -284,7 +323,30 @@ async def aiohttp_hook(self, request: aiohttp.web.Request) -> aiohttp.web.Respon
             )
 
         response = await self.on_interaction(body=body, signature=signature_header, timestamp=timestamp_header)
-        return aiohttp.web.Response(status=response.status_code, headers=response.headers, body=response.payload)
+
+        if response.files:
+            multipart = aiohttp.MultipartWriter(subtype="form-data")
+            if response.payload:
+                body_payload = aiohttp.BytesPayload(response.payload, content_type=response.content_type)
+                body_payload.set_content_disposition("form-data", name="payload_json")
+                multipart.append_payload(body_payload)
+
+            for index, file_ in enumerate(response.files):
+                async with file_.stream(head_only=True) as stream:
+                    mimetype = stream.mimetype or _APPLICATION_OCTET_STREAM
+
+                payload = _FilePayload(file_, mimetype, executor=self._executor)
+                payload.set_content_disposition("form-data", name=f"files[{index}]", filename=file_.filename)
+                multipart.append_payload(payload)
+
+            return aiohttp.web.Response(status=response.status_code, headers=response.headers, body=multipart)
+
+        headers = response.headers
+        if response.content_type:
+            headers = headers or {}
+            headers[_CONTENT_TYPE_KEY] = response.content_type
+
+        return aiohttp.web.Response(status=response.status_code, headers=headers, body=response.payload)
 
     async def close(self) -> None:
         """Gracefully close the server and any open connections."""
@@ -376,7 +438,8 @@ async def on_interaction(self, body: bytes, signature: bytes, timestamp: bytes)
             _LOGGER.debug("Dispatching interaction %s", interaction.id)
             try:
                 result = await listener(interaction)
-                payload = self._dumps(result.build(self._entity_factory))
+                raw_payload, files = result.build(self._entity_factory)
+                payload = self._dumps(raw_payload)
 
             except Exception as exc:
                 asyncio.get_running_loop().call_exception_handler(
@@ -384,7 +447,7 @@ async def on_interaction(self, body: bytes, signature: bytes, timestamp: bytes)
                 )
                 return _Response(_INTERNAL_SERVER_ERROR_STATUS, b"Exception occurred during interaction dispatch")
 
-            return _Response(_OK_STATUS, payload.encode(), content_type=_JSON_TYPE_WITH_CHARSET)
+            return _Response(_OK_STATUS, payload.encode(), files=files, content_type=_JSON_TYPE_WITH_CHARSET)
 
         _LOGGER.debug(
             "Ignoring interaction %s of type %s without registered listener", interaction.id, interaction.type
diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py
index a76ce8e0..a9e7c6c1 100644
--- a/hikari/impl/rest.py
+++ b/hikari/impl/rest.py
@@ -3249,7 +3249,7 @@ async def create_slash_command(
     async def create_context_menu_command(
         self,
         application: snowflakes.SnowflakeishOr[guilds.PartialApplication],
-        type: typing.Literal[commands.CommandType.USER, commands.CommandType.MESSAGE, 2, 3],
+        type: typing.Union[commands.CommandType, int],
         name: str,
         *,
         guild: undefined.UndefinedOr[snowflakes.SnowflakeishOr[guilds.PartialGuild]] = undefined.UNDEFINED,
diff --git a/hikari/impl/special_endpoints.py b/hikari/impl/special_endpoints.py
index 92605e26..772d1559 100644
--- a/hikari/impl/special_endpoints.py
+++ b/hikari/impl/special_endpoints.py
@@ -77,6 +77,7 @@
     from hikari import users
     from hikari import voices
     from hikari.api import entity_factory as entity_factory_
+    from hikari.api import rest as rest_api
 
     _T = typing.TypeVar("_T")
     _CommandBuilderT = typing.TypeVar("_CommandBuilderT", bound="CommandBuilder")
@@ -788,9 +789,11 @@ def set_choices(
         self._choices = choices
         return self
 
-    def build(self, _: entity_factory_.EntityFactory, /) -> data_binding.JSONObject:
+    def build(
+        self, _: entity_factory_.EntityFactory, /
+    ) -> typing.Tuple[data_binding.JSONObject, typing.Sequence[files.Resource[files.AsyncReader]]]:
         data = {"choices": [{"name": choice.name, "value": choice.value} for choice in self._choices]}
-        return {"type": self.type, "data": data}
+        return {"type": self.type, "data": data}, ()
 
 
 @attr_extensions.with_copy
@@ -828,11 +831,13 @@ def set_flags(
         self._flags = flags
         return self
 
-    def build(self, _: entity_factory_.EntityFactory, /) -> data_binding.JSONObject:
+    def build(
+        self, _: entity_factory_.EntityFactory, /
+    ) -> typing.Tuple[data_binding.JSONObject, typing.Sequence[files.Resource[files.AsyncReader]]]:
         if self._flags is not undefined.UNDEFINED:
-            return {"type": self._type, "data": {"flags": self._flags}}
+            return {"type": self._type, "data": {"flags": self._flags}}, ()
 
-        return {"type": self._type}
+        return {"type": self._type}, ()
 
 
 @attr_extensions.with_copy
@@ -873,20 +878,29 @@ class InteractionMessageBuilder(special_endpoints.InteractionMessageBuilder):
     _user_mentions: undefined.UndefinedOr[
         typing.Union[snowflakes.SnowflakeishSequence[users.PartialUser], bool]
     ] = attr.field(default=undefined.UNDEFINED, kw_only=True)
-    _components: typing.List[special_endpoints.ComponentBuilder] = attr.field(factory=list, kw_only=True)
-    _embeds: typing.List[embeds_.Embed] = attr.field(factory=list, kw_only=True)
+    _attachments: undefined.UndefinedOr[typing.List[files.Resourceish]] = attr.field(
+        default=undefined.UNDEFINED, kw_only=True
+    )
+    _components: undefined.UndefinedOr[typing.List[special_endpoints.ComponentBuilder]] = attr.field(
+        default=undefined.UNDEFINED, kw_only=True
+    )
+    _embeds: undefined.UndefinedOr[typing.List[embeds_.Embed]] = attr.field(default=undefined.UNDEFINED, kw_only=True)
+
+    @property
+    def attachments(self) -> undefined.UndefinedOr[typing.Sequence[files.Resourceish]]:
+        return self._attachments.copy() if self._attachments is not undefined.UNDEFINED else undefined.UNDEFINED
 
     @property
     def content(self) -> undefined.UndefinedOr[str]:
         return self._content
 
     @property
-    def components(self) -> typing.Sequence[special_endpoints.ComponentBuilder]:
-        return self._components.copy()
+    def components(self) -> undefined.UndefinedOr[typing.Sequence[special_endpoints.ComponentBuilder]]:
+        return self._components.copy() if self._components is not undefined.UNDEFINED else undefined.UNDEFINED
 
     @property
-    def embeds(self) -> typing.Sequence[embeds_.Embed]:
-        return self._embeds.copy()
+    def embeds(self) -> undefined.UndefinedOr[typing.Sequence[embeds_.Embed]]:
+        return self._embeds.copy() if self._embeds is not undefined.UNDEFINED else undefined.UNDEFINED
 
     @property
     def flags(self) -> typing.Union[undefined.UndefinedType, int, messages.MessageFlag]:
@@ -916,13 +930,28 @@ def user_mentions(
     ) -> undefined.UndefinedOr[typing.Union[snowflakes.SnowflakeishSequence[users.PartialUser], bool]]:
         return self._user_mentions
 
+    def add_attachment(
+        self: _InteractionMessageBuilderT, attachment: files.Resourceish, /
+    ) -> _InteractionMessageBuilderT:
+        if self._attachments is undefined.UNDEFINED:
+            self._attachments = []
+
+        self._attachments.append(attachment)
+        return self
+
     def add_component(
         self: _InteractionMessageBuilderT, component: special_endpoints.ComponentBuilder, /
     ) -> _InteractionMessageBuilderT:
+        if self._components is undefined.UNDEFINED:
+            self._components = []
+
         self._components.append(component)
         return self
 
     def add_embed(self: _InteractionMessageBuilderT, embed: embeds_.Embed, /) -> _InteractionMessageBuilderT:
+        if self._embeds is undefined.UNDEFINED:
+            self._embeds = []
+
         self._embeds.append(embed)
         return self
 
@@ -968,15 +997,22 @@ def set_user_mentions(
         self._user_mentions = user_mentions
         return self
 
-    def build(self, entity_factory: entity_factory_.EntityFactory, /) -> data_binding.JSONObject:
+    def build(
+        self, entity_factory: entity_factory_.EntityFactory, /
+    ) -> typing.Tuple[data_binding.JSONObject, typing.Sequence[files.Resource[files.AsyncReader]]]:
         data = data_binding.JSONObjectBuilder()
         data.put("content", self.content)
-        if self._embeds:
+
+        if self._attachments:
+            final_attachments = [files.ensure_resource(attachment) for attachment in self._attachments]
+
+        else:
+            final_attachments = []
+
+        if self._embeds is not undefined.UNDEFINED:
             embeds: typing.List[data_binding.JSONObject] = []
             for embed, attachments in map(entity_factory.serialize_embed, self._embeds):
-                if attachments:
-                    raise ValueError("Cannot send an embed with attachments in a slash command's initial response")
-
+                final_attachments.extend(attachments)
                 embeds.append(embed)
 
             data["embeds"] = embeds
@@ -985,12 +1021,15 @@ def build(self, entity_factory: entity_factory_.EntityFactory, /) -> data_bindin
         data.put("flags", self.flags)
         data.put("tts", self.is_tts)
 
-        if not undefined.all_undefined(self.mentions_everyone, self.user_mentions, self.role_mentions):
+        if (
+            not undefined.all_undefined(self.mentions_everyone, self.user_mentions, self.role_mentions)
+            or self.type is base_interactions.ResponseType.MESSAGE_CREATE
+        ):
             data["allowed_mentions"] = mentions.generate_allowed_mentions(
                 self.mentions_everyone, undefined.UNDEFINED, self.user_mentions, self.role_mentions
             )
 
-        return {"type": self._type, "data": data}
+        return {"type": self._type, "data": data}, final_attachments
 
 
 @attr.define(kw_only=False, weakref_slot=False)
@@ -1061,6 +1100,23 @@ def build(self, entity_factory: entity_factory_.EntityFactory, /) -> data_bindin
         data.put_array("options", self._options, conversion=entity_factory.serialize_command_option)
         return data
 
+    async def create(
+        self,
+        rest: rest_api.RESTClient,
+        application: snowflakes.SnowflakeishOr[guilds.PartialApplication],
+        /,
+        *,
+        guild: undefined.UndefinedOr[snowflakes.SnowflakeishOr[guilds.PartialGuild]] = undefined.UNDEFINED,
+    ) -> commands.SlashCommand:
+        return await rest.create_slash_command(
+            application,
+            self._name,
+            self._description,
+            guild=guild,
+            default_permission=self._default_permission,
+            options=self._options,
+        )
+
 
 @attr_extensions.with_copy
 @attr.define(kw_only=False, weakref_slot=False)
@@ -1075,6 +1131,18 @@ class ContextMenuCommandBuilder(CommandBuilder, special_endpoints.ContextMenuCom
     def type(self) -> commands.CommandType:
         return self._type
 
+    async def create(
+        self,
+        rest: rest_api.RESTClient,
+        application: snowflakes.SnowflakeishOr[guilds.PartialApplication],
+        /,
+        *,
+        guild: undefined.UndefinedOr[snowflakes.SnowflakeishOr[guilds.PartialGuild]] = undefined.UNDEFINED,
+    ) -> commands.ContextMenuCommand:
+        return await rest.create_context_menu_command(
+            application, self._type, self._name, guild=guild, default_permission=self._default_permission
+        )
+
 
 def _build_emoji(
     emoji: typing.Union[snowflakes.Snowflakeish, emojis.Emoji, str, undefined.UndefinedType] = undefined.UNDEFINED
diff --git a/hikari/internal/data_binding.py b/hikari/internal/data_binding.py
index d0c22fe2..b7390a42 100644
--- a/hikari/internal/data_binding.py
+++ b/hikari/internal/data_binding.py
@@ -46,7 +46,7 @@
 from hikari import undefined
 
 if typing.TYPE_CHECKING:
-    import concurrent
+    import concurrent.futures
     import contextlib
 
     T_co = typing.TypeVar("T_co", covariant=True)
@@ -87,9 +87,11 @@
 
     def dump_json(_: typing.Union[JSONArray, JSONObject], /, *, indent: int = ...) -> str:
         """Convert a Python type to a JSON string."""
+        raise NotImplementedError
 
     def load_json(_: typing.AnyStr, /) -> typing.Union[JSONArray, JSONObject]:
         """Convert a JSON string to a Python type."""
+        raise NotImplementedError
 
 else:
     import json
diff --git a/hikari/internal/routes.py b/hikari/internal/routes.py
index c6da70ea..66a66e76 100644
--- a/hikari/internal/routes.py
+++ b/hikari/internal/routes.py
@@ -546,7 +546,7 @@ def compile_to_file(
 CDN_GUILD_DISCOVERY_SPLASH: typing.Final[CDNRoute] = CDNRoute(
     "/discovery-splashes/{guild_id}/{hash}", {PNG, *JPEG_JPG, WEBP}
 )
-CDN_GUILD_BANNER: typing.Final[CDNRoute] = CDNRoute("/banners/{guild_id}/{hash}", {PNG, *JPEG_JPG, WEBP})
+CDN_GUILD_BANNER: typing.Final[CDNRoute] = CDNRoute("/banners/{guild_id}/{hash}", {PNG, *JPEG_JPG, WEBP, GIF})
 
 CDN_DEFAULT_USER_AVATAR: typing.Final[CDNRoute] = CDNRoute("/embed/avatars/{discriminator}", {PNG}, sizable=False)
 CDN_USER_AVATAR: typing.Final[CDNRoute] = CDNRoute("/avatars/{user_id}/{hash}", {PNG, *JPEG_JPG, WEBP, GIF})
diff --git a/hikari/invites.py b/hikari/invites.py
index 9e9380ca..694892b4 100644
--- a/hikari/invites.py
+++ b/hikari/invites.py
@@ -186,14 +186,18 @@ def banner_url(self) -> typing.Optional[files.URL]:
         """Banner URL for the guild, if set."""
         return self.make_banner_url()
 
-    def make_banner_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optional[files.URL]:
+    def make_banner_url(self, *, ext: typing.Optional[str] = None, size: int = 4096) -> typing.Optional[files.URL]:
         """Generate the guild's banner image URL, if set.
 
         Parameters
         ----------
-        ext : builtins.str
-            The extension to use for this URL, defaults to `png`.
-            Supports `png`, `jpeg`, `jpg` and `webp`.
+        ext : typing.Optional[builtins.str]
+            The ext to use for this URL, defaults to `png` or `gif`.
+            Supports `png`, `jpeg`, `jpg`, `webp` and `gif` (when
+            animated).
+
+            If `builtins.None`, then the correct default extension is
+            determined based on whether the banner is animated or not.
         size : builtins.int
             The size to set for the URL, defaults to `4096`.
             Can be any power of two between 16 and 4096.
@@ -211,6 +215,13 @@ def make_banner_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optio
         if self.banner_hash is None:
             return None
 
+        if ext is None:
+            if self.banner_hash.startswith("a_"):
+                ext = "gif"
+
+            else:
+                ext = "png"
+
         return routes.CDN_GUILD_BANNER.compile_to_file(
             urls.CDN_URL,
             guild_id=self.id,


Test patch:
diff --git a/tests/hikari/impl/test_interaction_server.py b/tests/hikari/impl/test_interaction_server.py
index c0cbd640..648ad2e4 100644
--- a/tests/hikari/impl/test_interaction_server.py
+++ b/tests/hikari/impl/test_interaction_server.py
@@ -24,9 +24,14 @@
 import re
 import threading
 
+import aiohttp
+import aiohttp.abc
 import aiohttp.web
 import aiohttp.web_runner
 import mock
+import multidict
+
+from hikari import files
 
 try:
     import nacl.exceptions
@@ -46,6 +51,29 @@
 from tests.hikari import hikari_test_helpers
 
 
+class MockWriter(aiohttp.abc.AbstractStreamWriter):
+    def __init__(self):
+        self.payload = bytearray()
+
+    async def write(self, chunk: bytes) -> None:
+        self.payload.extend(chunk)
+
+    async def write_eof(self, chunk: bytes = b"") -> None:
+        pass
+
+    async def drain(self) -> None:
+        pass
+
+    def enable_compression(self, encoding: str = "deflate") -> None:
+        pass
+
+    def enable_chunking(self) -> None:
+        pass
+
+    async def write_headers(self, status_line: str, headers: "multidict.CIMultiDict[str]") -> None:
+        pass
+
+
 @pytest.fixture()
 def valid_edd25519():
     body = (
@@ -129,31 +157,6 @@ def public_key():
     return b"\x12-\xdfX\xa8\x95\xd7\xe1\xb7o\xf5\xd0q\xb0\xaa\xc9\xb7v^*\xb5\x15\xe1\x1b\x7f\xca\xf9d\xdbT\x90\xc6"
 
 
-class Test_Response:
-    def test_when_only_status(self):
-        response = interaction_server_impl._Response(status_code=204)
-
-        assert response.payload is None
-        assert response.headers is None
-        assert response.status_code == 204
-
-    def test_defaults_to_text_content_type(self):
-        response = interaction_server_impl._Response(status_code=200, payload=b"hi there")
-
-        assert response.headers == {"Content-Type": "text/plain; charset=UTF-8"}
-        assert response.payload == b"hi there"
-        assert response.status_code == 200
-
-    def test_when_content_type_provided(self):
-        response = interaction_server_impl._Response(
-            status_code=201, payload=b'{"ok": "no"}', content_type="application/json"
-        )
-
-        assert response.headers == {"Content-Type": "application/json"}
-        assert response.payload == b'{"ok": "no"}'
-        assert response.status_code == 201
-
-
 @pytest.mark.skipif(nacl_present, reason="PyNacl is present")
 def test_interaction_server_init_when_no_pynacl():
     with pytest.raises(
@@ -176,11 +179,9 @@ def mock_rest_client(self):
         return mock.Mock(rest_impl.RESTClientImpl)
 
     @pytest.fixture()
-    def mock_verifier(self):
-        return mock.Mock()
-
-    @pytest.fixture()
-    def mock_interaction_server(self, mock_entity_factory, mock_rest_client, mock_verifier):
+    def mock_interaction_server(
+        self, mock_entity_factory: interaction_server_impl.InteractionServer, mock_rest_client: rest_impl.RESTClientImpl
+    ):
         cls = hikari_test_helpers.mock_class_namespace(interaction_server_impl.InteractionServer, slots_=False)
         stack = contextlib.ExitStack()
         stack.enter_context(mock.patch.object(rest_impl, "RESTClientImpl", return_value=mock_rest_client))
@@ -188,7 +189,9 @@ def mock_interaction_server(self, mock_entity_factory, mock_rest_client, mock_ve
         with stack:
             return cls(entity_factory=mock_entity_factory, rest_client=mock_rest_client)
 
-    def test___init__(self, mock_rest_client, mock_entity_factory, public_key):
+    def test___init__(
+        self, mock_rest_client: rest_impl.RESTClientImpl, mock_entity_factory: entity_factory_impl.EntityFactoryImpl
+    ):
         mock_dumps = object()
         mock_loads = object()
 
@@ -210,7 +213,9 @@ def test___init__(self, mock_rest_client, mock_entity_factory, public_key):
         assert result._rest_client is mock_rest_client
         assert result._public_key is None
 
-    def test___init___with_public_key(self, mock_rest_client, mock_entity_factory, public_key):
+    def test___init___with_public_key(
+        self, mock_rest_client: rest_impl.RESTClientImpl, mock_entity_factory: entity_factory_impl.EntityFactoryImpl
+    ):
         mock_dumps = object()
         mock_loads = object()
 
@@ -228,17 +233,19 @@ def test___init___with_public_key(self, mock_rest_client, mock_entity_factory, p
 
         assert result._public_key is None
 
-    def test_is_alive_property_when_inactive(self, mock_interaction_server):
+    def test_is_alive_property_when_inactive(self, mock_interaction_server: interaction_server_impl.InteractionServer):
         assert mock_interaction_server.is_alive is False
 
-    def test_is_alive_property_when_active(self, mock_interaction_server):
+    def test_is_alive_property_when_active(self, mock_interaction_server: interaction_server_impl.InteractionServer):
         mock_interaction_server._server = object()
 
         assert mock_interaction_server.is_alive is True
 
     @pytest.mark.asyncio()
     async def test___fetch_public_key_when_lock_is_None_gets_new_lock_and_doesnt_overwrite_existing_ones(
-        self, mock_interaction_server, mock_rest_client
+        self,
+        mock_interaction_server: interaction_server_impl.InteractionServer,
+        mock_rest_client: rest_impl.RESTClientImpl,
     ):
         mock_rest_client.token_type = "Bot"
         mock_interaction_server._application_fetch_lock = None
@@ -260,7 +267,11 @@ async def test___fetch_public_key_when_lock_is_None_gets_new_lock_and_doesnt_ove
         lock_class.return_value.__aexit__.assert_has_awaits([mock.call(None, None, None) for _ in range(5)])
 
     @pytest.mark.asyncio()
-    async def test__fetch_public_key_with_bearer_token(self, mock_interaction_server, mock_rest_client):
+    async def test__fetch_public_key_with_bearer_token(
+        self,
+        mock_interaction_server: interaction_server_impl.InteractionServer,
+        mock_rest_client: rest_impl.RESTClientImpl,
+    ):
         mock_rest_client.token_type = "Bearer"
         mock_interaction_server._application_fetch_lock = mock.AsyncMock()
         mock_rest_client.fetch_authorization.return_value.application.public_key = (
@@ -278,7 +289,11 @@ async def test__fetch_public_key_with_bearer_token(self, mock_interaction_server
         mock_interaction_server._application_fetch_lock.__aexit__.assert_awaited_once()
 
     @pytest.mark.asyncio()
-    async def test__fetch_public_key_fetch_with_bot_token(self, mock_interaction_server, mock_rest_client):
+    async def test__fetch_public_key_fetch_with_bot_token(
+        self,
+        mock_interaction_server: interaction_server_impl.InteractionServer,
+        mock_rest_client: rest_impl.RESTClientImpl,
+    ):
         mock_rest_client.token_type = "Bot"
         mock_interaction_server._application_fetch_lock = mock.AsyncMock()
         mock_rest_client.fetch_application.return_value.public_key = (
@@ -294,7 +309,9 @@ async def test__fetch_public_key_fetch_with_bot_token(self, mock_interaction_ser
         mock_interaction_server._application_fetch_lock.__aexit__.assert_awaited_once()
 
     @pytest.mark.asyncio()
-    async def test__fetch_public_key_when_public_key_already_set(self, mock_interaction_server):
+    async def test__fetch_public_key_when_public_key_already_set(
+        self, mock_interaction_server: interaction_server_impl.InteractionServer
+    ):
         mock_lock = mock.AsyncMock()
         mock_public_key = object()
         mock_interaction_server._application_fetch_lock = mock_lock
@@ -306,10 +323,10 @@ async def test__fetch_public_key_when_public_key_already_set(self, mock_interact
         mock_lock.__aexit__.assert_awaited_once()
 
     @pytest.mark.asyncio()
-    async def test_aiohttp_hook(self, mock_interaction_server):
+    async def test_aiohttp_hook(self, mock_interaction_server: interaction_server_impl.InteractionServer):
         mock_interaction_server.on_interaction = mock.AsyncMock(
             return_value=mock.Mock(
-                payload=b"abody", status_code=200, headers={"header1": "ok", "content-type": "oogabooga"}
+                payload=b"abody", files=[], status_code=200, headers={"header1": "ok"}, content_type="oogabooga"
             )
         )
         request = mock.Mock(
@@ -326,11 +343,84 @@ async def test_aiohttp_hook(self, mock_interaction_server):
         )
         assert result.body == b"abody"
         assert result.content_type == "oogabooga"
-        assert result.headers == {"header1": "ok", "content-type": "oogabooga"}
+        assert result.headers == {"header1": "ok", "Content-Type": "oogabooga"}
         assert result.status == 200
 
     @pytest.mark.asyncio()
-    async def test_aiohttp_hook_for_unsupported_media_type(self, mock_interaction_server):
+    async def test_aiohttp_hook_when_no_other_headers(
+        self, mock_interaction_server: interaction_server_impl.InteractionServer
+    ):
+        mock_interaction_server.on_interaction = mock.AsyncMock(
+            return_value=mock.Mock(payload=b"abody", files=[], headers=None, status_code=200, content_type="oogabooga")
+        )
+        request = mock.Mock(
+            aiohttp.web.Request,
+            content_type="application/json",
+            headers={"X-Signature-Ed25519": "74726f656b70657769656f6a6b736939", "X-Signature-Timestamp": "123123"},
+            read=mock.AsyncMock(return_value=b"bfddasdasd"),
+        )
+
+        result = await mock_interaction_server.aiohttp_hook(request)
+
+        mock_interaction_server.on_interaction.assert_awaited_once_with(
+            body=b"bfddasdasd", signature=b"troekpewieojksi9", timestamp=b"123123"
+        )
+        assert result.body == b"abody"
+        assert result.content_type == "oogabooga"
+        assert result.headers == {"Content-Type": "oogabooga"}
+        assert result.status == 200
+
+    @pytest.mark.asyncio()
+    async def test_aiohttp_hook_when_files(self, mock_interaction_server: interaction_server_impl.InteractionServer):
+        mock_interaction_server.on_interaction = mock.AsyncMock(
+            return_value=mock.Mock(
+                payload=b"abody",
+                files=[files.Bytes("x" * 329, "meow.txt"), files.Bytes("y" * 124, "nyaa.txt")],
+                status_code=200,
+                headers={"header1": "ok"},
+                content_type="oogabooga",
+            )
+        )
+        request = mock.Mock(
+            aiohttp.web.Request,
+            content_type="application/json",
+            headers={"X-Signature-Ed25519": "74726f656b70657769656f6a6b736939", "X-Signature-Timestamp": "123123"},
+            read=mock.AsyncMock(return_value=b"bfddasdasd"),
+        )
+
+        result = await mock_interaction_server.aiohttp_hook(request)
+
+        mock_interaction_server.on_interaction.assert_awaited_once_with(
+            body=b"bfddasdasd", signature=b"troekpewieojksi9", timestamp=b"123123"
+        )
+        assert isinstance(result.body, aiohttp.MultipartWriter)
+        assert result.content_type == "multipart/form-data"
+        assert result.headers == {
+            "header1": "ok",
+            "Content-Type": f"multipart/form-data; boundary={result.body.boundary}",
+        }
+        assert result.status == 200
+
+        mock_writer = MockWriter()
+        await result.body.write(mock_writer)
+
+        boundary = result.body.boundary.encode()
+        assert mock_writer.payload == (
+            b"--" + boundary + b"""\r\nContent-Type: oogabooga\r\nContent-Disposition: form-data; name="payload_json"""
+            b""""\r\nContent-Length: 5\r\n\r\nabody\r\n--""" + boundary + b"""\r\nContent-Type: text/plain\r\nConten"""
+            b"""t-Disposition: form-data; name="files[0]"; filename="meow.txt"\r\n\r\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"""
+            b"""xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"""
+            b"""xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"""
+            b"""xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"""
+            b"""\r\n--""" + boundary + b"""\r\nContent-Type: text/plain\r\nContent-Disposition: form-data; name="fil"""
+            b"""es[1]"; filename="nyaa.txt"\r\n\r\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"""
+            b"""yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\r\n--""" + boundary + b"""--\r\n"""
+        )
+
+    @pytest.mark.asyncio()
+    async def test_aiohttp_hook_for_unsupported_media_type(
+        self, mock_interaction_server: interaction_server_impl.InteractionServer
+    ):
         request = mock.Mock(aiohttp.web.Request, content_type="notjson")
 
         result = await mock_interaction_server.aiohttp_hook(request)
@@ -341,7 +431,9 @@ async def test_aiohttp_hook_for_unsupported_media_type(self, mock_interaction_se
         assert result.status == 415
 
     @pytest.mark.asyncio()
-    async def test_aiohttp_hook_with_missing_ed25519_header(self, mock_interaction_server):
+    async def test_aiohttp_hook_with_missing_ed25519_header(
+        self, mock_interaction_server: interaction_server_impl.InteractionServer
+    ):
         request = mock.Mock(aiohttp.web.Request, content_type="application/json", headers={"X-Signature-Timestamp": ""})
 
         result = await mock_interaction_server.aiohttp_hook(request)
@@ -352,7 +444,9 @@ async def test_aiohttp_hook_with_missing_ed25519_header(self, mock_interaction_s
         assert result.status == 400
 
     @pytest.mark.asyncio()
-    async def test_aiohttp_hook_with_missing_timestamp_header(self, mock_interaction_server):
+    async def test_aiohttp_hook_with_missing_timestamp_header(
+        self, mock_interaction_server: interaction_server_impl.InteractionServer
+    ):
         request = mock.Mock(aiohttp.web.Request, content_type="application/json", headers={"X-Signature-Ed25519": ""})
 
         result = await mock_interaction_server.aiohttp_hook(request)
@@ -363,7 +457,9 @@ async def test_aiohttp_hook_with_missing_timestamp_header(self, mock_interaction
         assert result.status == 400
 
     @pytest.mark.asyncio()
-    async def test_aiohttp_hook_with_invalid_ed25519_header(self, mock_interaction_server):
+    async def test_aiohttp_hook_with_invalid_ed25519_header(
+        self, mock_interaction_server: interaction_server_impl.InteractionServer
+    ):
         request = mock.Mock(
             aiohttp.web.Request,
             content_type="application/json",
@@ -378,7 +474,9 @@ async def test_aiohttp_hook_with_invalid_ed25519_header(self, mock_interaction_s
         assert result.status == 400
 
     @pytest.mark.asyncio()
-    async def test_aiohttp_hook_when_payload_too_large(self, mock_interaction_server):
+    async def test_aiohttp_hook_when_payload_too_large(
+        self, mock_interaction_server: interaction_server_impl.InteractionServer
+    ):
         request = mock.Mock(
             aiohttp.web.Request,
             content_type="application/json",
@@ -394,7 +492,7 @@ async def test_aiohttp_hook_when_payload_too_large(self, mock_interaction_server
         assert result.status == 413
 
     @pytest.mark.asyncio()
-    async def test_aiohttp_hook_when_no_body(self, mock_interaction_server):
+    async def test_aiohttp_hook_when_no_body(self, mock_interaction_server: interaction_server_impl.InteractionServer):
         request = mock.Mock(
             aiohttp.web.Request,
             content_type="application/json",
@@ -410,7 +508,7 @@ async def test_aiohttp_hook_when_no_body(self, mock_interaction_server):
         assert result.status == 400
 
     @pytest.mark.asyncio()
-    async def test_close(self, mock_interaction_server):
+    async def test_close(self, mock_interaction_server: interaction_server_impl.InteractionServer):
         mock_runner = mock.AsyncMock()
         mock_event = mock.Mock()
         mock_interaction_server._is_closing = False
@@ -425,7 +523,7 @@ async def test_close(self, mock_interaction_server):
         assert mock_interaction_server._is_closing is True
 
     @pytest.mark.asyncio()
-    async def test_close_when_closing(self, mock_interaction_server):
+    async def test_close_when_closing(self, mock_interaction_server: interaction_server_impl.InteractionServer):
         mock_runner = mock.AsyncMock()
         mock_event = mock.Mock()
         mock_interaction_server._server = mock_runner
@@ -441,7 +539,7 @@ async def test_close_when_closing(self, mock_interaction_server):
         mock_interaction_server.join.assert_awaited_once()
 
     @pytest.mark.asyncio()
-    async def test_close_when_not_running(self, mock_interaction_server):
+    async def test_close_when_not_running(self, mock_interaction_server: interaction_server_impl.InteractionServer):
         with pytest.raises(errors.ComponentStateConflictError):
             await mock_interaction_server.close()
 
@@ -456,19 +554,26 @@ async def test_join(self, mock_interaction_server):
         mock_event.wait.assert_awaited_once()
 
     @pytest.mark.asyncio()
-    async def test_join_when_not_running(self, mock_interaction_server):
+    async def test_join_when_not_running(self, mock_interaction_server: interaction_server_impl.InteractionServer):
         with pytest.raises(errors.ComponentStateConflictError):
             await mock_interaction_server.join()
 
     @pytest.mark.asyncio()
     async def test_on_interaction(
-        self, mock_interaction_server, mock_entity_factory, public_key, valid_edd25519, valid_payload
+        self,
+        mock_interaction_server: interaction_server_impl.InteractionServer,
+        mock_entity_factory: entity_factory_impl.EntityFactoryImpl,
+        public_key: bytes,
+        valid_edd25519: bytes,
+        valid_payload: bytes,
     ):
         mock_interaction_server._public_key = nacl.signing.VerifyKey(public_key)
+        mock_file_1 = mock.Mock()
+        mock_file_2 = mock.Mock()
         mock_entity_factory.deserialize_interaction.return_value = base_interactions.PartialInteraction(
             app=None, id=123, application_id=541324, type=2, token="ok", version=1
         )
-        mock_builder = mock.Mock(build=mock.Mock(return_value={"ok": "No boomer"}))
+        mock_builder = mock.Mock(build=mock.Mock(return_value=({"ok": "No boomer"}, [mock_file_1, mock_file_2])))
         mock_listener = mock.AsyncMock(return_value=mock_builder)
         mock_interaction_server.set_listener(base_interactions.PartialInteraction, mock_listener)
 
@@ -477,12 +582,16 @@ async def test_on_interaction(
         mock_listener.assert_awaited_once_with(mock_entity_factory.deserialize_interaction.return_value)
         mock_builder.build.assert_called_once_with(mock_entity_factory)
         mock_entity_factory.deserialize_interaction.assert_called_once_with(valid_payload)
-        assert result.headers == {"Content-Type": "application/json; charset=UTF-8"}
+        assert result.content_type == "application/json; charset=UTF-8"
+        assert result.files == [mock_file_1, mock_file_2]
+        assert result.headers is None
         assert result.payload == b'{"ok": "No boomer"}'
         assert result.status_code == 200
 
     @pytest.mark.asyncio()
-    async def test_on_interaction_calls__fetch_public_key(self, mock_interaction_server):
+    async def test_on_interaction_calls__fetch_public_key(
+        self, mock_interaction_server: interaction_server_impl.InteractionServer
+    ):
         mock_fetcher = mock.AsyncMock(
             return_value=mock.Mock(verify=mock.Mock(side_effect=nacl.exceptions.BadSignatureError))
         )
@@ -493,66 +602,93 @@ async def test_on_interaction_calls__fetch_public_key(self, mock_interaction_ser
 
         mock_fetcher.assert_awaited_once()
         mock_fetcher.return_value.verify.assert_called_once_with(b"timestampbody", b"signature")
-        assert result.headers == {"Content-Type": "text/plain; charset=UTF-8"}
+        assert result.content_type == "text/plain; charset=UTF-8"
+        assert result.files == ()
+        assert result.headers is None
         assert result.payload == b"Invalid request signature"
         assert result.status_code == 400
 
     @pytest.mark.asyncio()
-    async def test_on_interaction_when_public_key_mismatch(self, mock_interaction_server, public_key, invalid_ed25519):
+    async def test_on_interaction_when_public_key_mismatch(
+        self,
+        mock_interaction_server: interaction_server_impl.InteractionServer,
+        public_key: bytes,
+        invalid_ed25519: bytes,
+    ):
         mock_interaction_server._public_key = nacl.signing.VerifyKey(public_key)
 
         result = await mock_interaction_server.on_interaction(*invalid_ed25519)
 
-        assert result.headers == {"Content-Type": "text/plain; charset=UTF-8"}
+        assert result.content_type == "text/plain; charset=UTF-8"
+        assert result.files == ()
+        assert result.headers is None
         assert result.payload == b"Invalid request signature"
         assert result.status_code == 400
 
     @pytest.mark.parametrize("body", [b"not a json", b"\x80abc"])
     @pytest.mark.asyncio()
-    async def test_on_interaction_when_bad_body(self, mock_interaction_server, body):
+    async def test_on_interaction_when_bad_body(
+        self, mock_interaction_server: interaction_server_impl.InteractionServer, body: bytes
+    ):
         mock_interaction_server._public_key = mock.Mock()
 
         result = await mock_interaction_server.on_interaction(body, b"signature", b"timestamp")
 
-        assert result.headers == {"Content-Type": "text/plain; charset=UTF-8"}
+        assert result.content_type == "text/plain; charset=UTF-8"
+        assert result.files == ()
+        assert result.headers is None
         assert result.payload == b"Invalid JSON body"
         assert result.status_code == 400
 
     @pytest.mark.asyncio()
-    async def test_on_interaction_when_missing_type_key(self, mock_interaction_server):
+    async def test_on_interaction_when_missing_type_key(
+        self, mock_interaction_server: interaction_server_impl.InteractionServer
+    ):
         mock_interaction_server._public_key = mock.Mock()
 
         result = await mock_interaction_server.on_interaction(b'{"key": "OK"}', b"signature", b"timestamp")
 
-        assert result.headers == {"Content-Type": "text/plain; charset=UTF-8"}
+        assert result.content_type == "text/plain; charset=UTF-8"
+        assert result.files == ()
+        assert result.headers is None
         assert result.payload == b"Missing required 'type' field in payload"
         assert result.status_code == 400
 
     @pytest.mark.asyncio()
-    async def test_on_interaction_on_ping(self, mock_interaction_server):
+    async def test_on_interaction_on_ping(self, mock_interaction_server: interaction_server_impl.InteractionServer):
         mock_interaction_server._public_key = mock.Mock()
 
         result = await mock_interaction_server.on_interaction(b'{"type": 1}', b"signature", b"timestamp")
 
-        assert result.headers == {"Content-Type": "application/json; charset=UTF-8"}
+        assert result.content_type == "application/json; charset=UTF-8"
+        assert result.files == ()
+        assert result.headers is None
         assert result.payload == b'{"type": 1}'
         assert result.status_code == 200
 
     @pytest.mark.asyncio()
     async def test_on_interaction_on_deserialize_unrecognised_entity_error(
-        self, mock_interaction_server, mock_entity_factory
+        self,
+        mock_interaction_server: interaction_server_impl.InteractionServer,
+        mock_entity_factory: entity_factory_impl.EntityFactoryImpl,
     ):
         mock_interaction_server._public_key = mock.Mock()
         mock_entity_factory.deserialize_interaction.side_effect = errors.UnrecognisedEntityError("blah")
 
         result = await mock_interaction_server.on_interaction(b'{"type": 2}', b"signature", b"timestamp")
 
-        assert result.headers == {"Content-Type": "text/plain; charset=UTF-8"}
+        assert result.content_type == "text/plain; charset=UTF-8"
+        assert result.files == ()
+        assert result.headers is None
         assert result.payload == b"Interaction type not implemented"
         assert result.status_code == 501
 
     @pytest.mark.asyncio()
-    async def test_on_interaction_on_failed_deserialize(self, mock_interaction_server, mock_entity_factory):
+    async def test_on_interaction_on_failed_deserialize(
+        self,
+        mock_interaction_server: interaction_server_impl.InteractionServer,
+        mock_entity_factory: entity_factory_impl.EntityFactoryImpl,
+    ):
         mock_interaction_server._public_key = mock.Mock()
         mock_exception = TypeError("OK")
         mock_entity_factory.deserialize_interaction.side_effect = mock_exception
@@ -564,12 +700,18 @@ async def test_on_interaction_on_failed_deserialize(self, mock_interaction_serve
                 {"message": "Exception occurred during interaction deserialization", "exception": mock_exception}
             )
 
-        assert result.headers == {"Content-Type": "text/plain; charset=UTF-8"}
+        assert result.content_type == "text/plain; charset=UTF-8"
+        assert result.files == ()
+        assert result.headers is None
         assert result.payload == b"Exception occurred during interaction deserialization"
         assert result.status_code == 500
 
     @pytest.mark.asyncio()
-    async def test_on_interaction_on_dispatch_error(self, mock_interaction_server, mock_entity_factory):
+    async def test_on_interaction_on_dispatch_error(
+        self,
+        mock_interaction_server: interaction_server_impl.InteractionServer,
+        mock_entity_factory: entity_factory_impl.EntityFactoryImpl,
+    ):
         mock_interaction_server._public_key = mock.Mock()
         mock_exception = TypeError("OK")
         mock_entity_factory.deserialize_interaction.return_value = base_interactions.PartialInteraction(
@@ -586,12 +728,18 @@ async def test_on_interaction_on_dispatch_error(self, mock_interaction_server, m
                 {"message": "Exception occurred during interaction dispatch", "exception": mock_exception}
             )
 
-        assert result.headers == {"Content-Type": "text/plain; charset=UTF-8"}
+        assert result.content_type == "text/plain; charset=UTF-8"
+        assert result.files == ()
+        assert result.headers is None
         assert result.payload == b"Exception occurred during interaction dispatch"
         assert result.status_code == 500
 
     @pytest.mark.asyncio()
-    async def test_on_interaction_when_response_builder_error(self, mock_interaction_server, mock_entity_factory):
+    async def test_on_interaction_when_response_builder_error(
+        self,
+        mock_interaction_server: interaction_server_impl.InteractionServer,
+        mock_entity_factory: entity_factory_impl.EntityFactoryImpl,
+    ):
         mock_interaction_server._public_key = mock.Mock()
         mock_exception = TypeError("OK")
         mock_entity_factory.deserialize_interaction.return_value = base_interactions.PartialInteraction(
@@ -609,19 +757,25 @@ async def test_on_interaction_when_response_builder_error(self, mock_interaction
                 {"message": "Exception occurred during interaction dispatch", "exception": mock_exception}
             )
 
-        assert result.headers == {"Content-Type": "text/plain; charset=UTF-8"}
+        assert result.content_type == "text/plain; charset=UTF-8"
+        assert result.files == ()
+        assert result.headers is None
         assert result.payload == b"Exception occurred during interaction dispatch"
         assert result.status_code == 500
 
     @pytest.mark.asyncio()
-    async def test_on_interaction_when_json_encode_fails(self, mock_interaction_server, mock_entity_factory):
+    async def test_on_interaction_when_json_encode_fails(
+        self,
+        mock_interaction_server: interaction_server_impl.InteractionServer,
+        mock_entity_factory: entity_factory_impl.EntityFactoryImpl,
+    ):
         mock_interaction_server._public_key = mock.Mock()
         mock_exception = TypeError("OK")
         mock_interaction_server._dumps = mock.Mock(side_effect=mock_exception)
         mock_entity_factory.deserialize_interaction.return_value = base_interactions.PartialInteraction(
             app=None, id=123, application_id=541324, type=2, token="ok", version=1
         )
-        mock_builder = mock.Mock(build=mock.Mock())
+        mock_builder = mock.Mock(build=mock.Mock(return_value=({"ok": "No"}, [])))
         mock_interaction_server.set_listener(
             base_interactions.PartialInteraction, mock.AsyncMock(return_value=mock_builder)
         )
@@ -633,22 +787,30 @@ async def test_on_interaction_when_json_encode_fails(self, mock_interaction_serv
                 {"message": "Exception occurred during interaction dispatch", "exception": mock_exception}
             )
 
-        assert result.headers == {"Content-Type": "text/plain; charset=UTF-8"}
+        assert result.content_type == "text/plain; charset=UTF-8"
+        assert result.files == ()
+        assert result.headers is None
         assert result.payload == b"Exception occurred during interaction dispatch"
         assert result.status_code == 500
 
     @pytest.mark.asyncio()
-    async def test_on_interaction_when_no_registered_listener(self, mock_interaction_server, mock_entity_factory):
+    async def test_on_interaction_when_no_registered_listener(
+        self,
+        mock_interaction_server: interaction_server_impl.InteractionServer,
+        mock_entity_factory: entity_factory_impl.EntityFactoryImpl,
+    ):
         mock_interaction_server._public_key = mock.Mock()
 
         result = await mock_interaction_server.on_interaction(b'{"type": 2}', b"signature", b"timestamp")
 
-        assert result.headers == {"Content-Type": "text/plain; charset=UTF-8"}
+        assert result.content_type == "text/plain; charset=UTF-8"
+        assert result.files == ()
+        assert result.headers is None
         assert result.payload == b"Handler not set for this interaction type"
         assert result.status_code == 501
 
     @pytest.mark.asyncio()
-    async def test_start(self, mock_interaction_server):
+    async def test_start(self, mock_interaction_server: interaction_server_impl.InteractionServer):
         mock_context = object()
         mock_socket = object()
         mock_interaction_server._is_closing = True
@@ -713,7 +875,9 @@ async def test_start(self, mock_interaction_server):
             assert mock_interaction_server._is_closing is False
 
     @pytest.mark.asyncio()
-    async def test_start_with_default_behaviour(self, mock_interaction_server):
+    async def test_start_with_default_behaviour(
+        self, mock_interaction_server: interaction_server_impl.InteractionServer
+    ):
         mock_context = object()
         stack = contextlib.ExitStack()
         stack.enter_context(mock.patch.object(aiohttp.web, "TCPSite", return_value=mock.AsyncMock()))
@@ -743,7 +907,9 @@ async def test_start_with_default_behaviour(self, mock_interaction_server):
             aiohttp.web.TCPSite.return_value.start.assert_awaited_once()
 
     @pytest.mark.asyncio()
-    async def test_start_with_default_behaviour_and_not_main_thread(self, mock_interaction_server):
+    async def test_start_with_default_behaviour_and_not_main_thread(
+        self, mock_interaction_server: interaction_server_impl.InteractionServer
+    ):
         mock_context = object()
         stack = contextlib.ExitStack()
         stack.enter_context(mock.patch.object(aiohttp.web, "TCPSite", return_value=mock.AsyncMock()))
@@ -774,7 +940,7 @@ async def test_start_with_default_behaviour_and_not_main_thread(self, mock_inter
             aiohttp.web.TCPSite.return_value.start.assert_awaited_once()
 
     @pytest.mark.asyncio()
-    async def test_start_with_multiple_hosts(self, mock_interaction_server):
+    async def test_start_with_multiple_hosts(self, mock_interaction_server: interaction_server_impl.InteractionServer):
         mock_context = object()
         stack = contextlib.ExitStack()
         stack.enter_context(mock.patch.object(aiohttp.web, "TCPSite", return_value=mock.AsyncMock()))
@@ -819,35 +985,39 @@ async def test_start_with_multiple_hosts(self, mock_interaction_server):
             aiohttp.web.TCPSite.return_value.start.assert_has_awaits([mock.call(), mock.call()])
 
     @pytest.mark.asyncio()
-    async def test_start_when_already_running(self, mock_interaction_server):
+    async def test_start_when_already_running(self, mock_interaction_server: interaction_server_impl.InteractionServer):
         mock_interaction_server._server = object()
 
         with pytest.raises(errors.ComponentStateConflictError):
             await mock_interaction_server.start()
 
-    def test_get_listener_when_unknown(self, mock_interaction_server):
+    def test_get_listener_when_unknown(self, mock_interaction_server: interaction_server_impl.InteractionServer):
         assert mock_interaction_server.get_listener(base_interactions.PartialInteraction) is None
 
-    def test_get_listener_when_registered(self, mock_interaction_server):
+    def test_get_listener_when_registered(self, mock_interaction_server: interaction_server_impl.InteractionServer):
         mock_listener = object()
         mock_interaction_server.set_listener(base_interactions.PartialInteraction, mock_listener)
 
         assert mock_interaction_server.get_listener(base_interactions.PartialInteraction) is mock_listener
 
-    def test_set_listener(self, mock_interaction_server):
+    def test_set_listener(self, mock_interaction_server: interaction_server_impl.InteractionServer):
         mock_listener = object()
 
         mock_interaction_server.set_listener(base_interactions.PartialInteraction, mock_listener)
 
         assert mock_interaction_server.get_listener(base_interactions.PartialInteraction) is mock_listener
 
-    def test_set_listener_when_already_registered_without_replace(self, mock_interaction_server):
+    def test_set_listener_when_already_registered_without_replace(
+        self, mock_interaction_server: interaction_server_impl.InteractionServer
+    ):
         mock_interaction_server.set_listener(base_interactions.PartialInteraction, object())
 
         with pytest.raises(TypeError):
             mock_interaction_server.set_listener(base_interactions.PartialInteraction, object())
 
-    def test_set_listener_when_already_registered_with_replace(self, mock_interaction_server):
+    def test_set_listener_when_already_registered_with_replace(
+        self, mock_interaction_server: interaction_server_impl.InteractionServer
+    ):
         mock_listener = object()
         mock_interaction_server.set_listener(base_interactions.PartialInteraction, object())
 
@@ -855,7 +1025,9 @@ def test_set_listener_when_already_registered_with_replace(self, mock_interactio
 
         assert mock_interaction_server.get_listener(base_interactions.PartialInteraction) is mock_listener
 
-    def test_set_listener_when_removing_listener(self, mock_interaction_server):
+    def test_set_listener_when_removing_listener(
+        self, mock_interaction_server: interaction_server_impl.InteractionServer
+    ):
         mock_interaction_server.set_listener(base_interactions.PartialInteraction, object())
         mock_interaction_server.set_listener(base_interactions.PartialInteraction, None)
 
diff --git a/tests/hikari/impl/test_rest.py b/tests/hikari/impl/test_rest.py
index f6bf5f94..f821e524 100644
--- a/tests/hikari/impl/test_rest.py
+++ b/tests/hikari/impl/test_rest.py
@@ -1365,7 +1365,8 @@ def test_interaction_autocomplete_builder(self, rest_client):
         assert isinstance(result, special_endpoints.InteractionAutocompleteBuilder)
         assert len(result.choices) == 2
 
-        raw = result.build(mock.Mock())
+        raw, files = result.build(mock.Mock())
+        assert files == ()
         assert raw["data"] == {"choices": [{"name": "name", "value": "value"}, {"name": "a", "value": "b"}]}
 
     def test_interaction_autocomplete_builder_with_set_choices(self, rest_client):
diff --git a/tests/hikari/impl/test_special_endpoints.py b/tests/hikari/impl/test_special_endpoints.py
index 23bb80a0..9a482727 100644
--- a/tests/hikari/impl/test_special_endpoints.py
+++ b/tests/hikari/impl/test_special_endpoints.py
@@ -25,6 +25,7 @@
 
 from hikari import commands
 from hikari import emojis
+from hikari import files
 from hikari import messages
 from hikari import snowflakes
 from hikari import undefined
@@ -318,17 +319,23 @@ def test_set_flags(self):
     def test_build(self):
         builder = special_endpoints.InteractionDeferredBuilder(base_interactions.ResponseType.DEFERRED_MESSAGE_CREATE)
 
-        assert builder.build(object()) == {"type": base_interactions.ResponseType.DEFERRED_MESSAGE_CREATE}
+        result, attachments = builder.build(object())
+
+        assert result == {"type": base_interactions.ResponseType.DEFERRED_MESSAGE_CREATE}
+        assert attachments == ()
 
     def test_build_with_flags(self):
         builder = special_endpoints.InteractionDeferredBuilder(
             base_interactions.ResponseType.DEFERRED_MESSAGE_CREATE
         ).set_flags(64)
 
-        assert builder.build(object()) == {
+        result, attachments = builder.build(object())
+
+        assert result == {
             "type": base_interactions.ResponseType.DEFERRED_MESSAGE_CREATE,
             "data": {"flags": 64},
         }
+        assert attachments == ()
 
 
 class TestInteractionMessageBuilder:
@@ -348,26 +355,39 @@ def test_set_content_casts_to_str(self):
 
         assert builder.content == "meow nya"
 
-    def test_components_property(self):
-        mock_component = object()
+    def test_attachments_property(self):
+        mock_attachment = mock.Mock()
+        builder = special_endpoints.InteractionMessageBuilder(4).add_attachment(mock_attachment)
+
+        assert builder.attachments == [mock_attachment]
+
+    def test_attachments_property_when_undefined(self):
         builder = special_endpoints.InteractionMessageBuilder(4)
 
-        assert builder.components == []
+        assert builder.attachments is undefined.UNDEFINED
 
-        builder.add_component(mock_component)
+    def test_components_property(self):
+        mock_component = object()
+        builder = special_endpoints.InteractionMessageBuilder(4).add_component(mock_component)
 
         assert builder.components == [mock_component]
 
-    def test_embeds_property(self):
-        mock_embed = object()
+    def test_components_property_when_undefined(self):
         builder = special_endpoints.InteractionMessageBuilder(4)
 
-        assert builder.embeds == []
+        assert builder.components is undefined.UNDEFINED
 
-        builder.add_embed(mock_embed)
+    def test_embeds_property(self):
+        mock_embed = object()
+        builder = special_endpoints.InteractionMessageBuilder(4).add_embed(mock_embed)
 
         assert builder.embeds == [mock_embed]
 
+    def test_embeds_property_when_undefined(self):
+        builder = special_endpoints.InteractionMessageBuilder(4)
+
+        assert builder.embeds is undefined.UNDEFINED
+
     def test_flags_property(self):
         builder = special_endpoints.InteractionMessageBuilder(4).set_flags(95995)
 
@@ -411,7 +431,7 @@ def test_build(self):
             .set_role_mentions([54234])
         )
 
-        result = builder.build(mock_entity_factory)
+        result, attachments = builder.build(mock_entity_factory)
 
         mock_entity_factory.serialize_embed.assert_called_once_with(mock_embed)
         mock_component.build.assert_called_once_with()
@@ -426,18 +446,62 @@ def test_build(self):
                 "allowed_mentions": {"parse": [], "users": ["123"], "roles": ["54234"]},
             },
         }
+        assert attachments == []
+
+    def test_build_for_partial_when_message_create(self):
+        mock_entity_factory = mock.Mock()
+        builder = special_endpoints.InteractionMessageBuilder(base_interactions.ResponseType.MESSAGE_CREATE)
+
+        result, attachments = builder.build(mock_entity_factory)
+
+        mock_entity_factory.serialize_embed.assert_not_called()
+        assert result == {
+            "type": base_interactions.ResponseType.MESSAGE_CREATE,
+            "data": {"allowed_mentions": {"parse": []}},
+        }
+        assert attachments == []
+
+    def test_build_for_partial_when_message_update(self):
+        mock_entity_factory = mock.Mock()
+        builder = special_endpoints.InteractionMessageBuilder(base_interactions.ResponseType.MESSAGE_UPDATE)
+
+        result, attachments = builder.build(mock_entity_factory)
+
+        mock_entity_factory.serialize_embed.assert_not_called()
+        assert result == {"type": base_interactions.ResponseType.MESSAGE_UPDATE, "data": {}}
+        assert attachments == []
+
+    def test_build_for_partial_when_empty_lists(self):
+        mock_entity_factory = mock.Mock()
+        builder = special_endpoints.InteractionMessageBuilder(
+            base_interactions.ResponseType.MESSAGE_UPDATE, attachments=[], components=[], embeds=[]
+        )
+
+        result, attachments = builder.build(mock_entity_factory)
+
+        mock_entity_factory.serialize_embed.assert_not_called()
+        assert result == {
+            "type": base_interactions.ResponseType.MESSAGE_UPDATE,
+            "data": {
+                "components": [],
+                "embeds": [],
+            },
+        }
+        assert attachments == []
 
     def test_build_handles_attachments(self):
+        mock_attachment = mock.Mock()
+        mock_other_attachment = mock.Mock()
         mock_entity_factory = mock.Mock()
-        mock_entity_factory.serialize_embed.return_value = (object(), [object()])
-        builder = special_endpoints.InteractionMessageBuilder(base_interactions.ResponseType.MESSAGE_CREATE).add_embed(
-            object()
+        mock_entity_factory.serialize_embed.return_value = (object(), [mock_other_attachment])
+        builder = (
+            special_endpoints.InteractionMessageBuilder(base_interactions.ResponseType.MESSAGE_CREATE)
+            .add_attachment(mock_attachment)
+            .add_embed(object())
         )
 
-        with pytest.raises(
-            ValueError, match="Cannot send an embed with attachments in a slash command's initial response"
-        ):
-            builder.build(mock_entity_factory)
+        _, attachments = builder.build(mock_entity_factory)
+        assert attachments == [files.ensure_resource(mock_attachment), mock_other_attachment]
 
 
 class TestSlashCommandBuilder:
@@ -500,6 +564,45 @@ def test_build_without_optional_data(self):
 
         assert result == {"type": 1, "name": "we are numberr", "description": "oner", "options": []}
 
+    @pytest.mark.asyncio()
+    async def test_create(self):
+        builder = (
+            special_endpoints.SlashCommandBuilder("we are number", "one")
+            .add_option(mock.Mock())
+            .set_id(3412312)
+            .set_default_permission(False)
+        )
+        mock_rest = mock.AsyncMock()
+
+        result = await builder.create(mock_rest, 123431123)
+
+        assert result is mock_rest.create_slash_command.return_value
+        mock_rest.create_slash_command.assert_awaited_once_with(
+            123431123,
+            builder.name,
+            builder.description,
+            guild=undefined.UNDEFINED,
+            default_permission=builder.default_permission,
+            options=builder.options,
+        )
+
+    @pytest.mark.asyncio()
+    async def test_create_with_guild(self):
+        builder = special_endpoints.SlashCommandBuilder("we are number", "one")
+        mock_rest = mock.AsyncMock()
+
+        result = await builder.create(mock_rest, 54455445, guild=54123123321)
+
+        assert result is mock_rest.create_slash_command.return_value
+        mock_rest.create_slash_command.assert_awaited_once_with(
+            54455445,
+            builder.name,
+            builder.description,
+            guild=54123123321,
+            default_permission=builder.default_permission,
+            options=builder.options,
+        )
+
 
 class TestContextMenuBuilder:
     def test_build_with_optional_data(self):
@@ -525,6 +628,42 @@ def test_build_without_optional_data(self):
 
         assert result == {"type": 3, "name": "nameeeee"}
 
+    @pytest.mark.asyncio()
+    async def test_create(self):
+        builder = (
+            special_endpoints.ContextMenuCommandBuilder(commands.CommandType.USER, "we are number")
+            .set_id(3412312)
+            .set_default_permission(False)
+        )
+        mock_rest = mock.AsyncMock()
+
+        result = await builder.create(mock_rest, 123321)
+
+        assert result is mock_rest.create_context_menu_command.return_value
+        mock_rest.create_context_menu_command.assert_awaited_once_with(
+            123321,
+            builder.type,
+            builder.name,
+            guild=undefined.UNDEFINED,
+            default_permission=builder.default_permission,
+        )
+
+    @pytest.mark.asyncio()
+    async def test_create_with_guild(self):
+        builder = special_endpoints.ContextMenuCommandBuilder(commands.CommandType.MESSAGE, "we are number")
+        mock_rest = mock.AsyncMock()
+
+        result = await builder.create(mock_rest, 4444444, guild=765234123)
+
+        assert result is mock_rest.create_context_menu_command.return_value
+        mock_rest.create_context_menu_command.assert_awaited_once_with(
+            4444444,
+            builder.type,
+            builder.name,
+            guild=765234123,
+            default_permission=builder.default_permission,
+        )
+
 
 @pytest.mark.parametrize("emoji", ["UNICORN", emojis.UnicodeEmoji("UNICORN")])
 def test__build_emoji_with_unicode_emoji(emoji):
diff --git a/tests/hikari/test_guilds.py b/tests/hikari/test_guilds.py
index be274883..551481e1 100644
--- a/tests/hikari/test_guilds.py
+++ b/tests/hikari/test_guilds.py
@@ -1153,6 +1153,38 @@ def test_make_banner_url_when_hash(self, model):
             file_format="url",
         )
 
+    def test_make_banner_url_when_format_is_None_and_banner_hash_is_for_gif(self, model):
+        model.banner_hash = "a_18dnf8dfbakfdh"
+
+        with mock.patch.object(
+            routes, "CDN_GUILD_BANNER", new=mock.Mock(compile_to_file=mock.Mock(return_value="file"))
+        ) as route:
+            assert model.make_banner_url(ext=None, size=4096) == "file"
+
+        route.compile_to_file.assert_called_once_with(
+            urls.CDN_URL,
+            guild_id=model.id,
+            hash="a_18dnf8dfbakfdh",
+            size=4096,
+            file_format="gif",
+        )
+
+    def test_make_banner_url_when_format_is_None_and_banner_hash_is_not_for_gif(self, model):
+        model.banner_hash = "18dnf8dfbakfdh"
+
+        with mock.patch.object(
+            routes, "CDN_GUILD_BANNER", new=mock.Mock(compile_to_file=mock.Mock(return_value="file"))
+        ) as route:
+            assert model.make_banner_url(ext=None, size=4096) == "file"
+
+        route.compile_to_file.assert_called_once_with(
+            urls.CDN_URL,
+            guild_id=model.id,
+            hash=model.banner_hash,
+            size=4096,
+            file_format="png",
+        )
+
     def test_make_banner_url_when_no_hash(self, model):
         model.banner_hash = None
         assert model.make_banner_url(ext="png", size=2048) is None
diff --git a/tests/hikari/test_invites.py b/tests/hikari/test_invites.py
index b2fb983f..0c9e743e 100644
--- a/tests/hikari/test_invites.py
+++ b/tests/hikari/test_invites.py
@@ -20,8 +20,11 @@
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 # SOFTWARE.
 import mock
+import pytest
 
 from hikari import invites
+from hikari import urls
+from hikari.internal import routes
 from tests.hikari import hikari_test_helpers
 
 
@@ -33,6 +36,107 @@ def test_str_operator(self):
         assert str(mock_invite) == "https://discord.gg/hikari"
 
 
+class TestInviteGuild:
+    @pytest.fixture()
+    def model(self):
+        return invites.InviteGuild(
+            app=mock.Mock(),
+            id=123321,
+            icon_hash="hi",
+            name="bye",
+            features=[],
+            splash_hash="4o4o4o",
+            banner_hash="fofoof",
+            description="fkkfkf",
+            verification_level=1,
+            vanity_url_code=None,
+            welcome_screen=None,
+            nsfw_level=2,
+        )
+
+    def test_splash_url(self, model: invites.InviteGuild):
+        splash = object()
+
+        with mock.patch.object(invites.InviteGuild, "make_splash_url", return_value=splash):
+            assert model.splash_url is splash
+
+    def test_make_splash_url_when_hash(self, model: invites.InviteGuild):
+        model.splash_hash = "18dnf8dfbakfdh"
+
+        with mock.patch.object(
+            routes, "CDN_GUILD_SPLASH", new=mock.Mock(compile_to_file=mock.Mock(return_value="file"))
+        ) as route:
+            assert model.make_splash_url(ext="url", size=2) == "file"
+
+        route.compile_to_file.assert_called_once_with(
+            urls.CDN_URL,
+            guild_id=123321,
+            hash="18dnf8dfbakfdh",
+            size=2,
+            file_format="url",
+        )
+
+    def test_make_splash_url_when_no_hash(self, model: invites.InviteGuild):
+        model.splash_hash = None
+        assert model.make_splash_url(ext="png", size=1024) is None
+
+    def test_banner_url(self, model: invites.InviteGuild):
+        banner = object()
+
+        with mock.patch.object(invites.InviteGuild, "make_banner_url", return_value=banner):
+            assert model.banner_url is banner
+
+    def test_make_banner_url_when_hash(self, model: invites.InviteGuild):
+        with mock.patch.object(
+            routes, "CDN_GUILD_BANNER", new=mock.Mock(compile_to_file=mock.Mock(return_value="file"))
+        ) as route:
+            assert model.make_banner_url(ext="url", size=512) == "file"
+
+        route.compile_to_file.assert_called_once_with(
+            urls.CDN_URL,
+            guild_id=123321,
+            hash="fofoof",
+            size=512,
+            file_format="url",
+        )
+
+    def test_make_banner_url_when_format_is_None_and_banner_hash_is_for_gif(self, model: invites.InviteGuild):
+        model.banner_hash = "a_18dnf8dfbakfdh"
+
+        with mock.patch.object(
+            routes, "CDN_GUILD_BANNER", new=mock.Mock(compile_to_file=mock.Mock(return_value="file"))
+        ) as route:
+            assert model.make_banner_url(ext=None, size=4096) == "file"
+
+        route.compile_to_file.assert_called_once_with(
+            urls.CDN_URL,
+            guild_id=model.id,
+            hash="a_18dnf8dfbakfdh",
+            size=4096,
+            file_format="gif",
+        )
+
+    def test_make_banner_url_when_format_is_None_and_banner_hash_is_not_for_gif(self, model: invites.InviteGuild):
+        model.banner_hash = "18dnf8dfbakfdh"
+
+        with mock.patch.object(
+            routes, "CDN_GUILD_BANNER", new=mock.Mock(compile_to_file=mock.Mock(return_value="file"))
+        ) as route:
+            assert model.make_banner_url(ext=None, size=4096) == "file"
+
+        route.compile_to_file.assert_called_once_with(
+            urls.CDN_URL,
+            guild_id=model.id,
+            hash=model.banner_hash,
+            size=4096,
+            file_format="png",
+        )
+
+    def test_make_banner_url_when_no_hash(self, model: invites.InviteGuild):
+        model.banner_hash = None
+        assert model.make_banner_url(ext="png", size=2048) is None
+
+
 class TestInviteWithMetadata:
     def test_uses_left(self):
         mock_invite = hikari_test_helpers.mock_class_namespace(