Repository: hikari-py/hikari
Pull request #961 (2022-01-09 19:49:34)

Add URL methods and properties for rich presence assets.
### 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)
-->


FasterSpeeding: https://github.com/discord/discord-api-docs/pull/4317 The "dynamic" asset urls haven't been documented yet and might not be complete so I'm just opening this as a draft encase anybody has objections on how this is structured. Plus i'm not too sure if I want to add these urls to hikari.urls or not yet
FasterSpeeding: Lol tbh this is kinda fucked cause ~~Night~~ Discord decided to not document any of the 3rd party dynamic urls without giving any reasoning so ig I'll have to just leave those out

Patch:
diff --git a/changes/961.feature.md b/changes/961.feature.md
new file mode 100644
index 00000000..4a51bf85
--- /dev/null
+++ b/changes/961.feature.md
@@ -0,0 +1,1 @@
+Add URL methods and properties for rich presence assets.
diff --git a/changes/971.feature.md b/changes/971.feature.md
new file mode 100644
index 00000000..f4e3516d
--- /dev/null
+++ b/changes/971.feature.md
@@ -0,0 +1,1 @@
+Add ability to send attachments in an interaction initial response
diff --git a/changes/989.feature.md b/changes/989.feature.md
new file mode 100644
index 00000000..220f5bfd
--- /dev/null
+++ b/changes/989.feature.md
@@ -0,0 +1,3 @@
+Strip tokens in the standard bot impls and RESTApp.
+
+This helps avoids a common mistake with trailing new-lines which leads to confusing errors on request.
diff --git a/hikari/api/rest.py b/hikari/api/rest.py
index 627e6d8c..a30dd2e0 100644
--- a/hikari/api/rest.py
+++ b/hikari/api/rest.py
@@ -7469,6 +7469,8 @@ async def create_interaction_response(
         *,
         flags: typing.Union[int, messages_.MessageFlag, undefined.UndefinedType] = undefined.UNDEFINED,
         tts: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
+        attachment: undefined.UndefinedOr[files.Resourceish] = undefined.UNDEFINED,
+        attachments: undefined.UndefinedOr[typing.Sequence[files.Resourceish]] = undefined.UNDEFINED,
         component: undefined.UndefinedOr[special_endpoints.ComponentBuilder] = undefined.UNDEFINED,
         components: undefined.UndefinedOr[typing.Sequence[special_endpoints.ComponentBuilder]] = undefined.UNDEFINED,
         embed: undefined.UndefinedOr[embeds_.Embed] = undefined.UNDEFINED,
@@ -7510,6 +7512,12 @@ async def create_interaction_response(
             no `embeds` kwarg is provided, then this will instead
             update the embed. This allows for simpler syntax when
             sending an embed alone.
+        attachment : hikari.undefined.UndefinedOr[hikari.files.Resourceish],
+            If provided, the message attachment. This can be a resource,
+            or string of a path on your computer or a URL.
+        attachments : hikari.undefined.UndefinedOr[typing.Sequence[hikari.files.Resourceish]],
+            If provided, the message attachments. These can be resources, or
+            strings consisting of paths on your computer or URLs.
         component : hikari.undefined.UndefinedOr[hikari.api.special_endpoints.ComponentBuilder]
             If provided, builder object of the component to include in this message.
         components : hikari.undefined.UndefinedOr[typing.Sequence[hikari.api.special_endpoints.ComponentBuilder]]
diff --git a/hikari/impl/bot.py b/hikari/impl/bot.py
index d31213ad..f7f625b8 100644
--- a/hikari/impl/bot.py
+++ b/hikari/impl/bot.py
@@ -337,7 +337,7 @@ def __init__(
         self._http_settings = http_settings if http_settings is not None else config.HTTPSettings()
         self._intents = intents
         self._proxy_settings = proxy_settings if proxy_settings is not None else config.ProxySettings()
-        self._token = token
+        self._token = token.strip()
 
         # Caching
         cache_settings = cache_settings if cache_settings is not None else config.CacheSettings()
diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py
index 4ce19b23..cfedc4e8 100644
--- a/hikari/impl/entity_factory.py
+++ b/hikari/impl/entity_factory.py
@@ -2610,6 +2610,7 @@ def deserialize_member_presence(  # noqa: CFQ001 - Max function length
             if "assets" in activity_payload:
                 assets_payload = activity_payload["assets"]
                 assets = presence_models.ActivityAssets(
+                    application_id=application_id,
                     large_image=assets_payload.get("large_image"),
                     large_text=assets_payload.get("large_text"),
                     small_image=assets_payload.get("small_image"),
diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py
index df0b89ab..1e8a98c9 100644
--- a/hikari/impl/rest.py
+++ b/hikari/impl/rest.py
@@ -404,8 +404,11 @@ def acquire(
         provider = _RESTProvider(lambda: entity_factory, self._executor, lambda: rest_client)
         entity_factory = entity_factory_impl.EntityFactoryImpl(provider)
 
-        if token_type is None and isinstance(token, str):
-            token_type = applications.TokenType.BEARER
+        if isinstance(token, str):
+            token = token.strip()
+
+            if token_type is None:
+                token_type = applications.TokenType.BEARER
 
         rest_client = RESTClientImpl(
             cache=None,
@@ -1204,26 +1207,32 @@ async def fetch_message(
         assert isinstance(response, dict)
         return self._entity_factory.deserialize_message(response)
 
-    async def _create_message(
+    def _build_message_payload(  # noqa: C901- Function too complex
         self,
-        route: routes.CompiledRoute,
-        body: data_binding.JSONObjectBuilder,
-        query: typing.Optional[data_binding.StringMapBuilder] = None,
+        /,
         *,
-        no_auth: bool = False,
-        content: undefined.UndefinedOr[typing.Any],
-        attachment: undefined.UndefinedOr[files.Resourceish],
-        attachments: undefined.UndefinedOr[typing.Sequence[files.Resourceish]],
-        component: undefined.UndefinedOr[special_endpoints.ComponentBuilder] = undefined.UNDEFINED,
-        components: undefined.UndefinedOr[typing.Sequence[special_endpoints.ComponentBuilder]] = undefined.UNDEFINED,
-        embed: undefined.UndefinedOr[embeds_.Embed],
-        embeds: undefined.UndefinedOr[typing.Sequence[embeds_.Embed]],
-        tts: undefined.UndefinedOr[bool],
-        mentions_everyone: undefined.UndefinedOr[bool],
-        mentions_reply: undefined.UndefinedOr[bool],
-        user_mentions: undefined.UndefinedOr[typing.Union[snowflakes.SnowflakeishSequence[users.PartialUser], bool]],
-        role_mentions: undefined.UndefinedOr[typing.Union[snowflakes.SnowflakeishSequence[guilds.PartialRole], bool]],
-    ) -> messages_.Message:
+        content: undefined.UndefinedOr[typing.Any] = undefined.UNDEFINED,
+        attachment: undefined.UndefinedOr[files.Resourceish] = undefined.UNDEFINED,
+        attachments: undefined.UndefinedOr[typing.Sequence[files.Resourceish]] = undefined.UNDEFINED,
+        component: undefined.UndefinedNoneOr[special_endpoints.ComponentBuilder] = undefined.UNDEFINED,
+        components: undefined.UndefinedNoneOr[
+            typing.Sequence[special_endpoints.ComponentBuilder]
+        ] = undefined.UNDEFINED,
+        embed: undefined.UndefinedNoneOr[embeds_.Embed] = undefined.UNDEFINED,
+        embeds: undefined.UndefinedNoneOr[typing.Sequence[embeds_.Embed]] = undefined.UNDEFINED,
+        replace_attachments: bool = False,
+        flags: typing.Union[undefined.UndefinedType, int, messages_.MessageFlag] = undefined.UNDEFINED,
+        tts: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
+        mentions_everyone: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
+        mentions_reply: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
+        user_mentions: undefined.UndefinedOr[
+            typing.Union[snowflakes.SnowflakeishSequence[users.PartialUser], bool]
+        ] = undefined.UNDEFINED,
+        role_mentions: undefined.UndefinedOr[
+            typing.Union[snowflakes.SnowflakeishSequence[guilds.PartialRole], bool]
+        ] = undefined.UNDEFINED,
+        edit: bool = False,
+    ) -> typing.Tuple[data_binding.JSONObjectBuilder, typing.Optional[data_binding.URLEncodedFormBuilder]]:
         if not undefined.any_undefined(attachment, attachments):
             raise ValueError("You may only specify one of 'attachment' or 'attachments', not both")
 
@@ -1239,7 +1248,7 @@ async def _create_message(
                 "use 'attachment' (singular) instead?"
             )
 
-        if components is not undefined.UNDEFINED and not isinstance(components, typing.Collection):
+        if components not in _NONE_OR_UNDEFINED and not isinstance(components, typing.Collection):
             raise TypeError(
                 "You passed a non-collection to 'components', but this expects a collection. Maybe you meant to "
                 "use 'component' (singular) instead?"
@@ -1253,7 +1262,7 @@ async def _create_message(
 
         if undefined.all_undefined(embed, embeds) and isinstance(content, embeds_.Embed):
             # Syntactic sugar, common mistake to accidentally send an embed
-            # as the content, so lets detect this and fix it for the user.
+            # as the content, so let's detect this and fix it for the user.
             embed = content
             content = undefined.UNDEFINED
 
@@ -1261,7 +1270,7 @@ async def _create_message(
             content, (files.Resource, files.RAWISH_TYPES, os.PathLike)
         ):
             # Syntactic sugar, common mistake to accidentally send an attachment
-            # as the content, so lets detect this and fix it for the user. This
+            # as the content, so let's detect this and fix it for the user. This
             # will still then work with normal implicit embed attachments as
             # we work this out later.
             attachment = content
@@ -1270,49 +1279,64 @@ async def _create_message(
         final_attachments: typing.List[files.Resource[files.AsyncReader]] = []
         if attachment is not undefined.UNDEFINED:
             final_attachments.append(files.ensure_resource(attachment))
-        if attachments is not undefined.UNDEFINED:
+        elif attachments is not undefined.UNDEFINED:
             final_attachments.extend([files.ensure_resource(a) for a in attachments])
 
+        serialized_components: undefined.UndefinedOr[typing.List[data_binding.JSONObject]] = undefined.UNDEFINED
         if component is not undefined.UNDEFINED:
-            body.put("components", [component.build()])
+            if component is not None:
+                serialized_components = [component.build()]
+            else:
+                serialized_components = []
 
         elif components is not undefined.UNDEFINED:
-            body.put("components", [component.build() for component in components])
+            if components is not None:
+                serialized_components = [component.build() for component in components]
+            else:
+                serialized_components = []
 
         serialized_embeds: undefined.UndefinedOr[data_binding.JSONArray] = undefined.UNDEFINED
-
         if embed is not undefined.UNDEFINED:
-            embed_payload, embed_attachments = self._entity_factory.serialize_embed(embed)
-            final_attachments.extend(embed_attachments)
-            serialized_embeds = [embed_payload]
-        elif embeds is not undefined.UNDEFINED:
-            serialized_embeds = []
-            for embed in embeds:
+            if embed is not None:
                 embed_payload, embed_attachments = self._entity_factory.serialize_embed(embed)
                 final_attachments.extend(embed_attachments)
-                serialized_embeds.append(embed_payload)
+                serialized_embeds = [embed_payload]
 
-        body.put(
-            "allowed_mentions",
-            mentions.generate_allowed_mentions(mentions_everyone, mentions_reply, user_mentions, role_mentions),
-        )
-        body.put("content", content, conversion=str)
+            else:
+                serialized_embeds = []
+
+        elif embeds is not undefined.UNDEFINED:
+            serialized_embeds = []
+            if embeds is not None:
+                for e in embeds:
+                    embed_payload, embed_attachments = self._entity_factory.serialize_embed(e)
+                    final_attachments.extend(embed_attachments)
+                    serialized_embeds.append(embed_payload)
+
+        body = data_binding.JSONObjectBuilder()
+        body.put("content", content, conversion=lambda v: v if v is None else str(v))
         body.put("tts", tts)
+        body.put("flags", flags)
         body.put("embeds", serialized_embeds)
+        body.put("components", serialized_components)
+
+        if replace_attachments:
+            body.put("attachments", None)
+
+        if not edit or not undefined.all_undefined(mentions_everyone, mentions_reply, user_mentions, role_mentions):
+            body.put(
+                "allowed_mentions",
+                mentions.generate_allowed_mentions(mentions_everyone, mentions_reply, user_mentions, role_mentions),
+            )
 
         if final_attachments:
             form = data_binding.URLEncodedFormBuilder(executor=self._executor)
-            form.add_field("payload_json", data_binding.dump_json(body), content_type=_APPLICATION_JSON)
 
             for i, attachment in enumerate(final_attachments):
                 form.add_resource(f"file{i}", attachment)
+            return body, form
 
-            response = await self._request(route, form_builder=form, query=query, no_auth=no_auth)
-        else:
-            response = await self._request(route, json=body, query=query, no_auth=no_auth)
-
-        assert isinstance(response, dict)
-        return self._entity_factory.deserialize_message(response)
+        return body, None
 
     async def create_message(
         self,
@@ -1338,12 +1362,7 @@ async def create_message(
         ] = undefined.UNDEFINED,
     ) -> messages_.Message:
         route = routes.POST_CHANNEL_MESSAGES.compile(channel=channel)
-        body = data_binding.JSONObjectBuilder()
-        body.put("nonce", nonce)
-        body.put("message_reference", reply, conversion=lambda m: {"message_id": str(int(m))})
-        return await self._create_message(
-            route,
-            body,
+        body, form = self._build_message_payload(
             content=content,
             attachment=attachment,
             attachments=attachments,
@@ -1357,6 +1376,17 @@ async def create_message(
             user_mentions=user_mentions,
             role_mentions=role_mentions,
         )
+        body.put("nonce", nonce)
+        body.put("message_reference", reply, conversion=lambda m: {"message_id": str(int(m))})
+
+        if form is not None:
+            form.add_field("payload_json", data_binding.dump_json(body), content_type=_APPLICATION_JSON)
+            response = await self._request(route, form_builder=form)
+        else:
+            response = await self._request(route, json=body)
+
+        assert isinstance(response, dict)
+        return self._entity_factory.deserialize_message(response)
 
     async def crosspost_message(
         self,
@@ -1370,135 +1400,6 @@ async def crosspost_message(
         assert isinstance(response, dict)
         return self._entity_factory.deserialize_message(response)
 
-    async def _edit_message(  # noqa: C901 - Function too complex
-        self,
-        route: routes.CompiledRoute,
-        body: data_binding.JSONObjectBuilder,
-        *,
-        no_auth: bool = False,
-        content: undefined.UndefinedOr[typing.Any],
-        attachment: undefined.UndefinedOr[files.Resourceish],
-        attachments: undefined.UndefinedOr[typing.Sequence[files.Resourceish]],
-        component: undefined.UndefinedNoneOr[special_endpoints.ComponentBuilder] = undefined.UNDEFINED,
-        components: undefined.UndefinedNoneOr[
-            typing.Sequence[special_endpoints.ComponentBuilder]
-        ] = undefined.UNDEFINED,
-        embed: undefined.UndefinedNoneOr[embeds_.Embed],
-        embeds: undefined.UndefinedNoneOr[typing.Sequence[embeds_.Embed]],
-        replace_attachments: bool,
-        mentions_everyone: undefined.UndefinedOr[bool],
-        mentions_reply: undefined.UndefinedOr[bool],
-        user_mentions: undefined.UndefinedOr[typing.Union[snowflakes.SnowflakeishSequence[users.PartialUser], bool]],
-        role_mentions: undefined.UndefinedOr[typing.Union[snowflakes.SnowflakeishSequence[guilds.PartialRole], bool]],
-    ) -> messages_.Message:
-        if not undefined.any_undefined(attachment, attachments):
-            raise ValueError("You may only specify one of 'attachment' or 'attachments', not both")
-
-        if not undefined.any_undefined(component, components):
-            raise ValueError("You may only specify one of 'component' or 'components', not both")
-
-        if not undefined.any_undefined(embed, embeds):
-            raise ValueError("You may only specify one of 'embed' or 'embeds', not both")
-
-        if attachments is not undefined.UNDEFINED and not isinstance(attachments, typing.Collection):
-            raise TypeError(
-                "You passed a non-collection to 'attachments', but this expects a collection. Maybe you meant to "
-                "use 'attachment' (singular) instead?"
-            )
-
-        if components is not undefined.UNDEFINED and not isinstance(components, typing.Collection):
-            raise TypeError(
-                "You passed a non-collection to 'components', but this expects a collection. Maybe you meant to "
-                "use 'component' (singular) instead?"
-            )
-
-        if embeds not in _NONE_OR_UNDEFINED and not isinstance(embeds, typing.Collection):
-            raise TypeError(
-                "You passed a non-collection to 'embeds', but this expects a collection. Maybe you meant to "
-                "use 'embed' (singular) instead?"
-            )
-
-        if not undefined.all_undefined(mentions_everyone, mentions_reply, user_mentions, role_mentions):
-            body.put(
-                "allowed_mentions",
-                mentions.generate_allowed_mentions(mentions_everyone, mentions_reply, user_mentions, role_mentions),
-            )
-
-        if embed is undefined.UNDEFINED and isinstance(content, embeds_.Embed):
-            # Syntactic sugar, common mistake to accidentally send an embed
-            # as the content, so lets detect this and fix it for the user.
-            embed = content
-            content = undefined.UNDEFINED
-        elif undefined.all_undefined(attachment, attachments) and isinstance(
-            content, (files.Resource, files.RAWISH_TYPES, os.PathLike)
-        ):
-            # Syntactic sugar, common mistake to accidentally send an attachment
-            # as the content, so lets detect this and fix it for the user. This
-            # will still then work with normal implicit embed attachments as
-            # we work this out later.
-            attachment = content
-            content = undefined.UNDEFINED
-
-        if content is not None:
-            body.put("content", content, conversion=str)
-        else:
-            body.put("content", None)
-
-        final_attachments: typing.List[files.Resource[files.AsyncReader]] = []
-        if attachment is not undefined.UNDEFINED:
-            final_attachments.append(files.ensure_resource(attachment))
-        if attachments is not undefined.UNDEFINED:
-            final_attachments.extend([files.ensure_resource(a) for a in attachments])
-
-        serialized_components: typing.Optional[typing.List[data_binding.JSONObject]] = None
-        if component is not undefined.UNDEFINED:
-            if component is not None:
-                serialized_components = [component.build()]
-
-            body.put("components", serialized_components)
-
-        elif components is not undefined.UNDEFINED:
-            if components is not None:
-                serialized_components = [component.build() for component in components]
-
-            body.put("components", serialized_components)
-
-        serialized_embeds: data_binding.JSONArray = []
-        update_embeds = False
-        if embed is not undefined.UNDEFINED:
-            update_embeds = True
-            if embed is not None:
-                embed_payload, embed_attachments = self._entity_factory.serialize_embed(embed)
-                serialized_embeds.append(embed_payload)
-                final_attachments.extend(embed_attachments)
-        elif embeds is not undefined.UNDEFINED:
-            update_embeds = True
-            if embeds is not None:
-                for embed in embeds:
-                    embed_payload, embed_attachments = self._entity_factory.serialize_embed(embed)
-                    serialized_embeds.append(embed_payload)
-                    final_attachments.extend(embed_attachments)
-
-        if update_embeds:
-            body.put("embeds", serialized_embeds)
-
-        if replace_attachments:
-            body.put("attachments", None)
-
-        if final_attachments:
-            form_builder = data_binding.URLEncodedFormBuilder(executor=self._executor)
-            form_builder.add_field("payload_json", data_binding.dump_json(body), content_type=_APPLICATION_JSON)
-
-            for i, attachment in enumerate(final_attachments):
-                form_builder.add_resource(f"file{i}", attachment)
-
-            response = await self._request(route, form_builder=form_builder, no_auth=no_auth)
-        else:
-            response = await self._request(route, json=body, no_auth=no_auth)
-
-        assert isinstance(response, dict)
-        return self._entity_factory.deserialize_message(response)
-
     async def edit_message(
         self,
         channel: snowflakes.SnowflakeishOr[channels_.TextableChannel],
@@ -1522,14 +1423,10 @@ async def edit_message(
         role_mentions: undefined.UndefinedOr[
             typing.Union[snowflakes.SnowflakeishSequence[guilds.PartialRole], bool]
         ] = undefined.UNDEFINED,
-        flags: undefined.UndefinedOr[messages_.MessageFlag] = undefined.UNDEFINED,
+        flags: typing.Union[undefined.UndefinedType, int, messages_.MessageFlag] = undefined.UNDEFINED,
     ) -> messages_.Message:
         route = routes.PATCH_CHANNEL_MESSAGE.compile(channel=channel, message=message)
-        body = data_binding.JSONObjectBuilder()
-        body.put("flags", flags)
-        return await self._edit_message(
-            route,
-            body,
+        body, form = self._build_message_payload(
             content=content,
             attachment=attachment,
             attachments=attachments,
@@ -1538,12 +1435,23 @@ async def edit_message(
             embed=embed,
             embeds=embeds,
             replace_attachments=replace_attachments,
+            flags=flags,
             mentions_everyone=mentions_everyone,
             mentions_reply=mentions_reply,
             user_mentions=user_mentions,
             role_mentions=role_mentions,
+            edit=True,
         )
 
+        if form is not None:
+            form.add_field("payload_json", data_binding.dump_json(body), content_type=_APPLICATION_JSON)
+            response = await self._request(route, form_builder=form)
+        else:
+            response = await self._request(route, json=body)
+
+        assert isinstance(response, dict)
+        return self._entity_factory.deserialize_message(response)
+
     async def delete_message(
         self,
         channel: snowflakes.SnowflakeishOr[channels_.TextableChannel],
@@ -1844,17 +1752,10 @@ async def execute_webhook(
         webhook_id = webhook if isinstance(webhook, int) else webhook.webhook_id
         route = routes.POST_WEBHOOK_WITH_TOKEN.compile(webhook=webhook_id, token=token)
 
-        body = data_binding.JSONObjectBuilder()
-        body.put("username", username)
-        body.put("avatar_url", avatar_url, conversion=str)
-        body.put("flags", flags)
         query = data_binding.StringMapBuilder()
         query.put("wait", True)
-        return await self._create_message(
-            route,
-            body,
-            query,
-            no_auth=True,
+
+        body, form = self._build_message_payload(
             content=content,
             attachment=attachment,
             attachments=attachments,
@@ -1863,11 +1764,22 @@ async def execute_webhook(
             embed=embed,
             embeds=embeds,
             tts=tts,
+            flags=flags,
             mentions_everyone=mentions_everyone,
             user_mentions=user_mentions,
             role_mentions=role_mentions,
-            mentions_reply=undefined.UNDEFINED,
         )
+        body.put("username", username)
+        body.put("avatar_url", avatar_url, conversion=str)
+
+        if form is not None:
+            form.add_field("payload_json", data_binding.dump_json(body), content_type=_APPLICATION_JSON)
+            response = await self._request(route, form_builder=form, query=query, no_auth=True)
+        else:
+            response = await self._request(route, json=body, query=query, no_auth=True)
+
+        assert isinstance(response, dict)
+        return self._entity_factory.deserialize_message(response)
 
     async def fetch_webhook_message(
         self,
@@ -1909,10 +1821,8 @@ async def edit_webhook_message(
         # int(ExecutableWebhook) isn't guaranteed to be valid nor the ID used to execute this entity as a webhook.
         webhook_id = webhook if isinstance(webhook, int) else webhook.webhook_id
         route = routes.PATCH_WEBHOOK_MESSAGE.compile(webhook=webhook_id, token=token, message=message)
-        return await self._edit_message(
-            route,
-            data_binding.JSONObjectBuilder(),
-            no_auth=True,
+
+        body, form = self._build_message_payload(
             content=content,
             attachment=attachment,
             attachments=attachments,
@@ -1922,11 +1832,20 @@ async def edit_webhook_message(
             embeds=embeds,
             replace_attachments=replace_attachments,
             mentions_everyone=mentions_everyone,
-            mentions_reply=undefined.UNDEFINED,
             user_mentions=user_mentions,
             role_mentions=role_mentions,
+            edit=True,
         )
 
+        if form is not None:
+            form.add_field("payload_json", data_binding.dump_json(body), content_type=_APPLICATION_JSON)
+            response = await self._request(route, form_builder=form, no_auth=True)
+        else:
+            response = await self._request(route, json=body, no_auth=True)
+
+        assert isinstance(response, dict)
+        return self._entity_factory.deserialize_message(response)
+
     async def delete_webhook_message(
         self,
         webhook: typing.Union[webhooks.ExecutableWebhook, snowflakes.Snowflakeish],
@@ -3485,10 +3404,12 @@ async def create_interaction_response(
         interaction: snowflakes.SnowflakeishOr[base_interactions.PartialInteraction],
         token: str,
         response_type: typing.Union[int, base_interactions.ResponseType],
-        content: undefined.UndefinedOr[typing.Any] = undefined.UNDEFINED,
+        content: undefined.UndefinedNoneOr[typing.Any] = undefined.UNDEFINED,
         *,
         flags: typing.Union[int, messages_.MessageFlag, undefined.UndefinedType] = undefined.UNDEFINED,
         tts: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
+        attachment: undefined.UndefinedOr[files.Resourceish] = undefined.UNDEFINED,
+        attachments: undefined.UndefinedOr[typing.Sequence[files.Resourceish]] = undefined.UNDEFINED,
         component: undefined.UndefinedOr[special_endpoints.ComponentBuilder] = undefined.UNDEFINED,
         components: undefined.UndefinedOr[typing.Sequence[special_endpoints.ComponentBuilder]] = undefined.UNDEFINED,
         embed: undefined.UndefinedOr[embeds_.Embed] = undefined.UNDEFINED,
@@ -3501,69 +3422,31 @@ async def create_interaction_response(
             typing.Union[snowflakes.SnowflakeishSequence[guilds.PartialRole], bool]
         ] = undefined.UNDEFINED,
     ) -> None:
-        if not undefined.any_undefined(component, components):
-            raise ValueError("You may only specify one of 'component' or 'components', not both")
-
-        if not undefined.any_undefined(embed, embeds):
-            raise ValueError("You may only specify one of 'embed' or 'embeds', not both")
-
-        if components is not undefined.UNDEFINED and not isinstance(components, typing.Collection):
-            raise TypeError(
-                "You passed a non-collection to 'components', but this expects a collection. Maybe you meant to "
-                "use 'component' (singular) instead?"
-            )
-
-        if embeds not in _NONE_OR_UNDEFINED and not isinstance(embeds, typing.Collection):
-            raise TypeError(
-                "You passed a non-collection to 'embeds', but this expects a collection. Maybe you meant to "
-                "use 'embed' (singular) instead?"
-            )
-
-        if undefined.all_undefined(embed, embeds) and isinstance(content, embeds_.Embed):
-            # Syntactic sugar, common mistake to accidentally send an embed
-            # as the content, so lets detect this and fix it for the user.
-            embed = content
-            content = undefined.UNDEFINED
-
         route = routes.POST_INTERACTION_RESPONSE.compile(interaction=interaction, token=token)
 
+        data, form = self._build_message_payload(
+            content=content,
+            attachment=attachment,
+            attachments=attachments,
+            component=component,
+            components=components,
+            embed=embed,
+            embeds=embeds,
+            tts=tts,
+            flags=flags,
+            mentions_everyone=mentions_everyone,
+            user_mentions=user_mentions,
+            role_mentions=role_mentions,
+        )
         body = data_binding.JSONObjectBuilder()
         body.put("type", response_type)
-
-        data = data_binding.JSONObjectBuilder()
-        data.put("content", content, conversion=str)
-        data.put("flags", flags)
-        data.put("tts", tts)
-        data.put(
-            "allowed_mentions",
-            mentions.generate_allowed_mentions(mentions_everyone, undefined.UNDEFINED, user_mentions, role_mentions),
-        )
-
-        if component is not undefined.UNDEFINED:
-            data.put("components", [component.build()])
-
-        elif components is not undefined.UNDEFINED:
-            data.put("components", [component.build() for component in components])
-
-        if embed is not undefined.UNDEFINED:
-            embed_payload, attachments = self._entity_factory.serialize_embed(embed)
-            if attachments:
-                raise ValueError("Cannot send an embed with attachments in a slash command's initial response")
-
-            data.put("embeds", [embed_payload])
-
-        elif embeds is not undefined.UNDEFINED:
-            embed_payloads: data_binding.JSONArray = []
-            for embed in embeds:
-                serialized_embed, attachments = self._entity_factory.serialize_embed(embed)
-                embed_payloads.append(serialized_embed)
-                if attachments:
-                    raise ValueError("Cannot send an embed with attachments in a slash command's initial response")
-
-            data.put("embeds", embed_payloads)
-
         body.put("data", data)
-        await self._request(route, json=body, no_auth=True)
+
+        if form is not None:
+            form.add_field("payload_json", data_binding.dump_json(body), content_type=_APPLICATION_JSON)
+            await self._request(route, form_builder=form, no_auth=True)
+        else:
+            await self._request(route, json=body, no_auth=True)
 
     async def edit_interaction_response(
         self,
@@ -3589,10 +3472,8 @@ async def edit_interaction_response(
         ] = undefined.UNDEFINED,
     ) -> messages_.Message:
         route = routes.PATCH_INTERACTION_RESPONSE.compile(webhook=application, token=token)
-        return await self._edit_message(
-            route,
-            data_binding.JSONObjectBuilder(),
-            no_auth=True,
+
+        body, form = self._build_message_payload(
             content=content,
             attachment=attachment,
             attachments=attachments,
@@ -3604,9 +3485,18 @@ async def edit_interaction_response(
             mentions_everyone=mentions_everyone,
             user_mentions=user_mentions,
             role_mentions=role_mentions,
-            mentions_reply=undefined.UNDEFINED,
+            edit=True,
         )
 
+        if form is not None:
+            form.add_field("payload_json", data_binding.dump_json(body), content_type=_APPLICATION_JSON)
+            response = await self._request(route, form_builder=form, no_auth=True)
+        else:
+            response = await self._request(route, json=body, no_auth=True)
+
+        assert isinstance(response, dict)
+        return self._entity_factory.deserialize_message(response)
+
     async def delete_interaction_response(
         self, application: snowflakes.SnowflakeishOr[guilds.PartialApplication], token: str
     ) -> None:
diff --git a/hikari/impl/rest_bot.py b/hikari/impl/rest_bot.py
index 428e6241..7bbc0ad1 100644
--- a/hikari/impl/rest_bot.py
+++ b/hikari/impl/rest_bot.py
@@ -252,6 +252,9 @@ def __init__(
         if isinstance(public_key, str):
             public_key = bytes.fromhex(public_key)
 
+        if isinstance(token, str):
+            token = token.strip()
+
         # Beautification and logging
         ux.init_logging(logs, allow_color, force_color)
         self.print_banner(banner, allow_color, force_color)
diff --git a/hikari/interactions/base_interactions.py b/hikari/interactions/base_interactions.py
index 7cf5c47d..019547ad 100644
--- a/hikari/interactions/base_interactions.py
+++ b/hikari/interactions/base_interactions.py
@@ -244,6 +244,8 @@ async def create_initial_response(
         *,
         flags: typing.Union[int, messages.MessageFlag, undefined.UndefinedType] = undefined.UNDEFINED,
         tts: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
+        attachment: undefined.UndefinedOr[files.Resourceish] = undefined.UNDEFINED,
+        attachments: undefined.UndefinedOr[typing.Sequence[files.Resourceish]] = undefined.UNDEFINED,
         component: undefined.UndefinedOr[special_endpoints.ComponentBuilder] = undefined.UNDEFINED,
         components: undefined.UndefinedOr[typing.Sequence[special_endpoints.ComponentBuilder]] = undefined.UNDEFINED,
         embed: undefined.UndefinedOr[embeds_.Embed] = undefined.UNDEFINED,
@@ -280,6 +282,12 @@ async def create_initial_response(
             If this is a `hikari.embeds.Embed` and no `embed` nor `embeds` kwarg
             is provided, then this will instead update the embed. This allows
             for simpler syntax when sending an embed alone.
+        attachment : hikari.undefined.UndefinedOr[hikari.files.Resourceish],
+            If provided, the message attachment. This can be a resource,
+            or string of a path on your computer or a URL.
+        attachments : hikari.undefined.UndefinedOr[typing.Sequence[hikari.files.Resourceish]],
+            If provided, the message attachments. These can be resources, or
+            strings consisting of paths on your computer or URLs.
         component : hikari.undefined.UndefinedOr[hikari.api.special_endpoints.ComponentBuilder]
             If provided, builder object of the component to include in this message.
         components : hikari.undefined.UndefinedOr[typing.Sequence[hikari.api.special_endpoints.ComponentBuilder]]
@@ -354,6 +362,8 @@ async def create_initial_response(
             response_type,
             content,
             tts=tts,
+            attachment=attachment,
+            attachments=attachments,
             component=component,
             components=components,
             embed=embed,
diff --git a/hikari/internal/routes.py b/hikari/internal/routes.py
index bccb87a7..674d2cd0 100644
--- a/hikari/internal/routes.py
+++ b/hikari/internal/routes.py
@@ -548,6 +548,7 @@ def compile_to_file(
 
 CDN_APPLICATION_ICON: typing.Final[CDNRoute] = CDNRoute("/app-icons/{application_id}/{hash}", {PNG, *JPEG_JPG, WEBP})
 CDN_APPLICATION_COVER: typing.Final[CDNRoute] = CDNRoute("/app-assets/{application_id}/{hash}", {PNG, *JPEG_JPG, WEBP})
+CDN_APPLICATION_ASSET: typing.Final[CDNRoute] = CDNRoute("/app-assets/{application_id}/{hash}", {PNG, *JPEG_JPG, WEBP})
 CDN_ACHIEVEMENT_ICON: typing.Final[CDNRoute] = CDNRoute(
     "/app-assets/{application_id}/achievements/{achievement_id}/icons/{hash}", {PNG, *JPEG_JPG, WEBP}
 )
diff --git a/hikari/presences.py b/hikari/presences.py
index 67fd1b55..8c03e74c 100644
--- a/hikari/presences.py
+++ b/hikari/presences.py
@@ -42,9 +42,12 @@
 
 import attr
 
+from hikari import files
 from hikari import snowflakes
+from hikari import urls
 from hikari.internal import attr_extensions
 from hikari.internal import enums
+from hikari.internal import routes
 
 if typing.TYPE_CHECKING:
     import datetime
@@ -118,11 +121,16 @@ class ActivityParty:
     """Maximum size of this party, if applicable."""
 
 
+_DYNAMIC_URLS = {"mp": urls.MEDIA_PROXY_URL + "/{}"}
+
+
 @attr_extensions.with_copy
 @attr.define(hash=False, kw_only=True, weakref_slot=False)
 class ActivityAssets:
     """Used to represent possible assets for an activity."""
 
+    _application_id: typing.Optional[snowflakes.Snowflake] = attr.field(repr=False)
+
     large_image: typing.Optional[str] = attr.field(repr=False)
     """The ID of the asset's large image, if set."""
 
@@ -135,6 +143,113 @@ class ActivityAssets:
     small_text: typing.Optional[str] = attr.field(repr=True)
     """The text that'll appear when hovering over the small image, if set."""
 
+    def _make_asset_url(self, asset: typing.Optional[str], ext: str, size: int) -> typing.Optional[files.URL]:
+        if asset is None:
+            return None
+
+        try:
+            resource, identifier = asset.split(":", 1)
+            return files.URL(url=_DYNAMIC_URLS[resource].format(identifier))
+
+        except KeyError:
+            raise RuntimeError("Unknown asset type") from None
+
+        except ValueError:
+            assert self._application_id is not None
+            return routes.CDN_APPLICATION_ASSET.compile_to_file(
+                urls.CDN_URL,
+                application_id=self._application_id,
+                hash=asset,
+                size=size,
+                file_format=ext,
+            )
+
+    @property
+    def large_image_url(self) -> typing.Optional[files.URL]:
+        """Large image asset URL.
+
+        !!! note
+            This will be `builtins.None` if no large image asset exists or if the
+            asset's dymamic URL (indicated by a `{name}:` prefix) is not known.
+        """
+        try:
+            return self.make_large_image_url()
+
+        except RuntimeError:
+            return None
+
+    def make_large_image_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optional[files.URL]:
+        """Generate the large image asset URL for this application.
+
+        !!! note
+            `ext` and `size` are ignored for images hosted outside of Discord
+            or on Discord's media proxy.
+
+        Parameters
+        ----------
+        ext : builtins.str
+            The extension to use for this URL, defaults to `png`.
+            Supports `png`, `jpeg`, `jpg` and `webp`.
+        size : builtins.int
+            The size to set for the URL, defaults to `4096`.
+            Can be any power of two between 16 and 4096.
+
+        Returns
+        -------
+        typing.Optional[hikari.files.URL]
+            The URL, or `builtins.None` if no icon exists.
+
+        Raises
+        ------
+        builtins.ValueError
+            If the size is not an integer power of 2 between 16 and 4096
+            (inclusive).
+        builtins.RuntimeError
+            If `ActivityAssets.large_image` points towards an unknown asset type.
+        """
+        return self._make_asset_url(self.large_image, ext, size)
+
+    @property
+    def small_image_url(self) -> typing.Optional[files.URL]:
+        """Small image asset URL.
+
+        !!! note
+            This will be `builtins.None` if no large image asset exists or if the
+            asset's dymamic URL (indicated by a `{name}:` prefix) is not known.
+        """
+        try:
+            return self.make_small_image_url()
+
+        except RuntimeError:
+            return None
+
+    def make_small_image_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optional[files.URL]:
+        """Generate the small image asset URL for this application.
+
+        Parameters
+        ----------
+        ext : builtins.str
+            The extension to use for this URL, defaults to `png`.
+            Supports `png`, `jpeg`, `jpg` and `webp`.
+        size : builtins.int
+            The size to set for the URL, defaults to `4096`.
+            Can be any power of two between 16 and 4096.
+
+        Returns
+        -------
+        typing.Optional[hikari.files.URL]
+            The URL, or `builtins.None` if no icon exists.
+
+        Raises
+        ------
+        builtins.ValueError
+            If the size is not an integer power of 2 between 16 and 4096
+            (inclusive).
+        builtins.RuntimeError
+            If `ActivityAssets.small_image` points towards an unknown asset type.
+        """
+        return self._make_asset_url(self.small_image, ext, size)
+
 
 @attr_extensions.with_copy
 @attr.define(hash=False, kw_only=True, weakref_slot=False)
diff --git a/hikari/urls.py b/hikari/urls.py
index fa0b67d3..b58fbb5c 100644
--- a/hikari/urls.py
+++ b/hikari/urls.py
@@ -39,3 +39,6 @@
 
 CDN_URL: typing.Final[str] = "https://cdn.discordapp.com"
 """The CDN URL."""
+
+MEDIA_PROXY_URL: typing.Final[str] = "https://media.discordapp.net"
+"""The media proxy URL."""
diff --git a/pipelines/nox.py b/pipelines/nox.py
index d0a9ce5e..9cbef36a 100644
--- a/pipelines/nox.py
+++ b/pipelines/nox.py
@@ -32,7 +32,7 @@
 from pipelines import config
 
 # Default sessions should be defined here
-_options.sessions = ["reformat-code", "pytest", "flake8", "mypy", "verify-types", "safety", "pages"]
+_options.sessions = ["reformat-code", "pytest", "flake8", "mypy", "verify-types"]
 
 _NoxCallbackSig = typing.Callable[[Session], None]
 
diff --git a/scripts/deploy.sh b/scripts/deploy.sh
index b71db85b..bdefd07b 100755
--- a/scripts/deploy.sh
+++ b/scripts/deploy.sh
@@ -22,8 +22,7 @@
 set -e
 
 if [ $(ls -1 changes/*.*.md 2>/dev/null | wc -l) != 0 ]; then
-    echo "Cannot create release if CHANGELOG fragment files exist under 'changes/'!"
-    exit 1
+    echo "Cannot create release if CHANGELOG fragment files exist under 'changes/'!" && exit 1
 fi
 
 echo "Defined environment variables"
@@ -42,6 +41,15 @@ if [ -z "${TWINE_USERNAME}" ]; then echo '$TWINE_USERNAME environment variable i
 if [ -z ${TWINE_PASSWORD+x} ]; then echo '$TWINE_PASSWORD environment variable is missing' && exit 1; fi
 if [ -z "${TWINE_PASSWORD}" ]; then echo '$TWINE_PASSWORD environment variable is empty' && exit 1; fi
 
+regex='__version__: typing\.Final\[str\] = "([^"]*)"'
+if [[ $(cat hikari/_about.py) =~ $regex ]]; then
+  if [ "${BASH_REMATCH[1]}" != "${VERSION}" ]; then
+    echo "Variable '__version__' does not match the version this deploy is for!" && exit 1
+  fi
+else
+  echo "Variable '__version__' not found in about!" && exit 1
+fi
+
 echo "===== INSTALLING DEPENDENCIES ====="
 # Note: We install each of these separately due to issues with the new PIP resolver
 # https://github.com/pypa/pip/issues/9187
@@ -53,7 +61,7 @@ pip install -r requirements.txt
 
 echo "===== DEPLOYING TO PYPI ====="
 echo "-- Setting __git_sha1__ (ref: ${REF}) --"
-sed "s|^__git_sha1__.*|__git_sha1__: typing.Final[str] = \"${REF}\"|g" -i hikari/_about.py
+sed "/^__git__sha1__.*/, \${s||__git__sha1__: typing.Final[str] = \"${REF}\"|g; b}; $q1" -i hikari/_about.py || (echo "Variable '__git__sha1__' not found in about!" && exit 1)
 echo "=========================================================================="
 cat hikari/_about.py
 echo "=========================================================================="
@@ -85,7 +93,7 @@ git fetch origin
 git checkout -f master
 
 echo "-- Bumping to development version (${NEW_VERSION}) --"
-sed "s|^__version__.*|__version__: typing.Final[str] = \"${NEW_VERSION}\"|g" -i hikari/_about.py
+sed "/^__version__.*/, \${s||__version__: typing.Final[str] = \"${NEW_VERSION}\"|g; b}; $q1" -i hikari/_about.py || (echo "Variable '__version__' not found in about!" && exit 1)
 
 echo "-- Pushing to repository --"
 git commit -am "Bump to development version (${NEW_VERSION})"
diff --git a/scripts/prepare-release.sh b/scripts/prepare-release.sh
index ce861762..858ee922 100644
--- a/scripts/prepare-release.sh
+++ b/scripts/prepare-release.sh
@@ -38,7 +38,7 @@ echo "-- Checkout branch --"
 git checkout -b "task/prepare-release-${VERSION}"
 
 echo "-- Bumping repository version to ${VERSION} --"
-sed "s|^__version__.*|__version__: typing.Final[str] = \"${VERSION}\"|g" -i hikari/_about.py
+sed "/^__version__.*/, \${s||__version__: typing.Final[str] = \"${VERSION}\"|g; b}; $q1" -i hikari/_about.py || (echo "Variable '__version__' not found in about!" && exit 1)
 
 echo "-- Running towncrier --"
 towncrier --yes


Test patch:
diff --git a/tests/hikari/hikari_test_helpers.py b/tests/hikari/hikari_test_helpers.py
index bb2b3b15..6e584837 100644
--- a/tests/hikari/hikari_test_helpers.py
+++ b/tests/hikari/hikari_test_helpers.py
@@ -49,16 +49,11 @@
 # condition, and thus acceptable to terminate the test and fail it.
 REASONABLE_TIMEOUT_AFTER = 10
 
-_stubbed_classes = {}
-
-
-def _stub_init(self, kwargs: typing.Mapping[str, typing.Any]):
-    for attr, value in kwargs.items():
-        setattr(self, attr, value)
+_T = typing.TypeVar("_T")
 
 
 def mock_class_namespace(
-    klass,
+    klass: typing.Type[_T],
     /,
     *,
     init_: bool = True,
@@ -66,7 +61,7 @@ def mock_class_namespace(
     implement_abstract_methods_: bool = True,
     rename_impl_: bool = True,
     **namespace: typing.Any,
-):
+) -> typing.Type[_T]:
     """Get a version of a class with the provided namespace fields set as class attributes."""
     if slots_ or slots_ is None and hasattr(klass, "__slots__"):
         namespace["__slots__"] = ()
diff --git a/tests/hikari/impl/test_bot.py b/tests/hikari/impl/test_bot.py
index 9a0d8920..8d1cc85e 100644
--- a/tests/hikari/impl/test_bot.py
+++ b/tests/hikari/impl/test_bot.py
@@ -252,6 +252,18 @@ def test_init_when_no_settings(self):
         cache.assert_called_once_with(bot, cache_settings.return_value)
         cache_settings.assert_called_once_with()
 
+    def test_init_strips_token(self):
+        stack = contextlib.ExitStack()
+        stack.enter_context(mock.patch.object(ux, "init_logging"))
+        stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "print_banner"))
+
+        with stack:
+            bot = bot_impl.GatewayBot(
+                "\n\r token yeet \r\n", cache_settings=None, http_settings=None, proxy_settings=None
+            )
+
+        assert bot._token == "token yeet"
+
     def test_cache(self, bot, cache):
         assert bot.cache is cache
 
diff --git a/tests/hikari/impl/test_entity_factory.py b/tests/hikari/impl/test_entity_factory.py
index 7469617c..173f8e8c 100644
--- a/tests/hikari/impl/test_entity_factory.py
+++ b/tests/hikari/impl/test_entity_factory.py
@@ -4634,6 +4634,7 @@ def test_deserialize_member_presence(
         assert isinstance(activity.party, presence_models.ActivityParty)
         # ActivityAssets
         assert activity.assets is not None
+        assert activity.assets._application_id is activity.application_id
         assert activity.assets.large_image == "34234234234243"
         assert activity.assets.large_text == "LARGE TEXT"
         assert activity.assets.small_image == "3939393"
diff --git a/tests/hikari/impl/test_rest.py b/tests/hikari/impl/test_rest.py
index 5d01d648..e948badc 100644
--- a/tests/hikari/impl/test_rest.py
+++ b/tests/hikari/impl/test_rest.py
@@ -35,6 +35,7 @@
 from hikari import colors
 from hikari import commands
 from hikari import config
+from hikari import embeds
 from hikari import emojis
 from hikari import errors
 from hikari import files
@@ -53,6 +54,7 @@
 from hikari.impl import rest
 from hikari.impl import special_endpoints
 from hikari.internal import data_binding
+from hikari.internal import mentions
 from hikari.internal import net
 from hikari.internal import routes
 from hikari.internal import time
@@ -475,7 +477,7 @@ def rest_client(rest_client_class, live_attributes, mock_cache):
         token_type="tYpe",
         max_retries=0,
         rest_url="https://some.where/api/v3",
-        executor=mock.Mock(),
+        executor=object(),
         entity_factory=mock.Mock(),
     )
     obj._live_attributes = live_attributes
@@ -1894,78 +1896,351 @@ async def test_fetch_message(self, rest_client):
         rest_client._request.assert_awaited_once_with(expected_route)
         rest_client._entity_factory.deserialize_message.assert_called_once_with({"id": "456"})
 
+    def test__build_message_payload_with_undefined_args(self, rest_client):
+        with mock.patch.object(
+            mentions, "generate_allowed_mentions", return_value={"allowed_mentions": 1}
+        ) as generate_allowed_mentions:
+            body, form = rest_client._build_message_payload()
+
+        assert body == {"allowed_mentions": {"allowed_mentions": 1}}
+        assert form is None
+
+        generate_allowed_mentions.assert_called_once_with(
+            undefined.UNDEFINED, undefined.UNDEFINED, undefined.UNDEFINED, undefined.UNDEFINED
+        )
+
+    @pytest.mark.parametrize("args", [("embeds", "components"), ("embed", "component")])
+    def test__build_message_payload_with_None_args(self, rest_client, args):
+        kwargs = {}
+        for arg in args:
+            kwargs[arg] = None
+
+        with mock.patch.object(
+            mentions, "generate_allowed_mentions", return_value={"allowed_mentions": 1}
+        ) as generate_allowed_mentions:
+            body, form = rest_client._build_message_payload(**kwargs)
+
+        assert body == {"embeds": [], "components": [], "allowed_mentions": {"allowed_mentions": 1}}
+        assert form is None
+
+        generate_allowed_mentions.assert_called_once_with(
+            undefined.UNDEFINED, undefined.UNDEFINED, undefined.UNDEFINED, undefined.UNDEFINED
+        )
+
+    def test__build_message_payload_with_edit_and_all_mentions_undefined(self, rest_client):
+        with mock.patch.object(mentions, "generate_allowed_mentions") as generate_allowed_mentions:
+            body, form = rest_client._build_message_payload(edit=True)
+
+        assert body == {}
+        assert form is None
+
+        generate_allowed_mentions.assert_not_called()
+
+    def test__build_message_payload_embed_content_syntactic_sugar(self, rest_client):
+        embed = mock.Mock(embeds.Embed)
+
+        stack = contextlib.ExitStack()
+        generate_allowed_mentions = stack.enter_context(
+            mock.patch.object(mentions, "generate_allowed_mentions", return_value={"allowed_mentions": 1})
+        )
+        rest_client._entity_factory.serialize_embed.return_value = ({"embed": 1}, [])
+
+        with stack:
+            body, form = rest_client._build_message_payload(content=embed)
+
+        # Returned
+        assert body == {"embeds": [{"embed": 1}], "allowed_mentions": {"allowed_mentions": 1}}
+        assert form is None
+
+        # Embeds
+        rest_client._entity_factory.serialize_embed.assert_called_once_with(embed)
+
+        # Generate allowed mentions
+        generate_allowed_mentions.assert_called_once_with(
+            undefined.UNDEFINED, undefined.UNDEFINED, undefined.UNDEFINED, undefined.UNDEFINED
+        )
+
+    def test__build_message_payload_attachment_content_syntactic_sugar(self, rest_client):
+        attachment = mock.Mock(files.Resource)
+        resource_attachment = object()
+
+        stack = contextlib.ExitStack()
+        ensure_resource = stack.enter_context(
+            mock.patch.object(files, "ensure_resource", return_value=resource_attachment)
+        )
+        generate_allowed_mentions = stack.enter_context(
+            mock.patch.object(mentions, "generate_allowed_mentions", return_value={"allowed_mentions": 1})
+        )
+        url_encoded_form = stack.enter_context(mock.patch.object(data_binding, "URLEncodedFormBuilder"))
+
+        with stack:
+            body, form = rest_client._build_message_payload(content=attachment)
+
+        # Returned
+        assert body == {"allowed_mentions": {"allowed_mentions": 1}}
+        assert form is url_encoded_form.return_value
+
+        # Attachments
+        ensure_resource.assert_called_once_with(attachment)
+
+        # Generate allowed mentions
+        generate_allowed_mentions.assert_called_once_with(
+            undefined.UNDEFINED, undefined.UNDEFINED, undefined.UNDEFINED, undefined.UNDEFINED
+        )
+
+        # Form builder
+        url_encoded_form.assert_called_once_with(executor=rest_client._executor)
+        url_encoded_form.return_value.add_resource.assert_called_once_with("file0", resource_attachment)
+
+    def test__build_message_payload_with_singular_args(self, rest_client):
+        attachment = object()
+        resource_attachment = object()
+        component = mock.Mock(build=mock.Mock(return_value={"component": 1}))
+        embed = object()
+        embed_attachment = object()
+        mentions_everyone = object()
+        mentions_reply = object()
+        user_mentions = object()
+        role_mentions = object()
+
+        stack = contextlib.ExitStack()
+        ensure_resource = stack.enter_context(
+            mock.patch.object(files, "ensure_resource", return_value=resource_attachment)
+        )
+        generate_allowed_mentions = stack.enter_context(
+            mock.patch.object(mentions, "generate_allowed_mentions", return_value={"allowed_mentions": 1})
+        )
+        url_encoded_form = stack.enter_context(mock.patch.object(data_binding, "URLEncodedFormBuilder"))
+        rest_client._entity_factory.serialize_embed.return_value = ({"embed": 1}, [embed_attachment])
+
+        with stack:
+            body, form = rest_client._build_message_payload(
+                content=987654321,
+                attachment=attachment,
+                component=component,
+                embed=embed,
+                replace_attachments=True,
+                flags=120,
+                tts=True,
+                mentions_everyone=mentions_everyone,
+                mentions_reply=mentions_reply,
+                user_mentions=user_mentions,
+                role_mentions=role_mentions,
+            )
+
+        # Returned
+        assert body == {
+            "content": "987654321",
+            "tts": True,
+            "flags": 120,
+            "embeds": [{"embed": 1}],
+            "components": [{"component": 1}],
+            "attachments": None,
+            "allowed_mentions": {"allowed_mentions": 1},
+        }
+        assert form is url_encoded_form.return_value
+
+        # Attachments
+        ensure_resource.assert_called_once_with(attachment)
+
+        # Embeds
+        rest_client._entity_factory.serialize_embed.assert_called_once_with(embed)
+
+        # Components
+        component.build.assert_called_once_with()
+
+        # Generate allowed mentions
+        generate_allowed_mentions.assert_called_once_with(
+            mentions_everyone, mentions_reply, user_mentions, role_mentions
+        )
+
+        # Form builder
+        url_encoded_form.assert_called_once_with(executor=rest_client._executor)
+        assert url_encoded_form.return_value.add_resource.call_count == 2
+        url_encoded_form.return_value.add_resource.assert_has_calls(
+            [mock.call("file0", resource_attachment), mock.call("file1", embed_attachment)]
+        )
+
+    def test__build_message_payload_with_plural_args(self, rest_client):
+        attachment1 = object()
+        attachment2 = object()
+        resource_attachment1 = object()
+        resource_attachment2 = object()
+        component1 = mock.Mock(build=mock.Mock(return_value={"component": 1}))
+        component2 = mock.Mock(build=mock.Mock(return_value={"component": 2}))
+        embed1 = object()
+        embed2 = object()
+        embed_attachment1 = object()
+        embed_attachment2 = object()
+        embed_attachment3 = object()
+        embed_attachment4 = object()
+        mentions_everyone = object()
+        mentions_reply = object()
+        user_mentions = object()
+        role_mentions = object()
+
+        stack = contextlib.ExitStack()
+        ensure_resource = stack.enter_context(
+            mock.patch.object(files, "ensure_resource", side_effect=[resource_attachment1, resource_attachment2])
+        )
+        generate_allowed_mentions = stack.enter_context(
+            mock.patch.object(mentions, "generate_allowed_mentions", return_value={"allowed_mentions": 1})
+        )
+        url_encoded_form = stack.enter_context(mock.patch.object(data_binding, "URLEncodedFormBuilder"))
+        rest_client._entity_factory.serialize_embed.side_effect = [
+            ({"embed": 1}, [embed_attachment1, embed_attachment2]),
+            ({"embed": 2}, [embed_attachment3, embed_attachment4]),
+        ]
+
+        with stack:
+            body, form = rest_client._build_message_payload(
+                content=987654321,
+                attachments=[attachment1, attachment2],
+                components=[component1, component2],
+                embeds=[embed1, embed2],
+                replace_attachments=True,
+                flags=120,
+                tts=True,
+                mentions_everyone=mentions_everyone,
+                mentions_reply=mentions_reply,
+                user_mentions=user_mentions,
+                role_mentions=role_mentions,
+            )
+
+        # Returned
+        assert body == {
+            "content": "987654321",
+            "tts": True,
+            "flags": 120,
+            "embeds": [{"embed": 1}, {"embed": 2}],
+            "components": [{"component": 1}, {"component": 2}],
+            "attachments": None,
+            "allowed_mentions": {"allowed_mentions": 1},
+        }
+        assert form is url_encoded_form.return_value
+
+        # Attachments
+        assert ensure_resource.call_count == 2
+        ensure_resource.assert_has_calls([mock.call(attachment1), mock.call(attachment2)])
+
+        # Embeds
+        assert rest_client._entity_factory.serialize_embed.call_count == 2
+        rest_client._entity_factory.serialize_embed.assert_has_calls([mock.call(embed1), mock.call(embed2)])
+
+        # Components
+        component1.build.assert_called_once_with()
+        component2.build.assert_called_once_with()
+
+        # Generate allowed mentions
+        generate_allowed_mentions.assert_called_once_with(
+            mentions_everyone, mentions_reply, user_mentions, role_mentions
+        )
+
+        # Form builder
+        url_encoded_form.assert_called_once_with(executor=rest_client._executor)
+        assert url_encoded_form.return_value.add_resource.call_count == 6
+        url_encoded_form.return_value.add_resource.assert_has_calls(
+            [
+                mock.call("file0", resource_attachment1),
+                mock.call("file1", resource_attachment2),
+                mock.call("file2", embed_attachment1),
+                mock.call("file3", embed_attachment2),
+                mock.call("file4", embed_attachment3),
+                mock.call("file5", embed_attachment4),
+            ]
+        )
+
     @pytest.mark.parametrize(
         ("singular_arg", "plural_arg"),
         [("attachment", "attachments"), ("component", "components"), ("embed", "embeds")],
     )
-    async def test__create_message_when_both_single_and_plural_args_passed(self, rest_client, singular_arg, plural_arg):
-        kwargs = {
-            "content": "testing",
-            "attachment": undefined.UNDEFINED,
-            "attachments": undefined.UNDEFINED,
-            "component": undefined.UNDEFINED,
-            "components": undefined.UNDEFINED,
-            "embed": undefined.UNDEFINED,
-            "embeds": undefined.UNDEFINED,
-            "tts": undefined.UNDEFINED,
-            "mentions_everyone": undefined.UNDEFINED,
-            "mentions_reply": undefined.UNDEFINED,
-            "user_mentions": undefined.UNDEFINED,
-            "role_mentions": undefined.UNDEFINED,
-            # Keys will be replaced
-            singular_arg: object(),
-            plural_arg: object(),
-        }
-
+    def test__build_message_payload_when_both_single_and_plural_args_passed(
+        self, rest_client, singular_arg, plural_arg
+    ):
         with pytest.raises(
             ValueError, match=rf"You may only specify one of '{singular_arg}' or '{plural_arg}', not both"
         ):
-            await rest_client._create_message(None, {}, **kwargs)
+            rest_client._build_message_payload(**{singular_arg: object(), plural_arg: object()})
 
     @pytest.mark.parametrize(
         ("singular_arg", "plural_arg"),
         [("attachment", "attachments"), ("component", "components"), ("embed", "embeds")],
     )
-    async def test__create_message_when_non_collection_passed_to_plural(self, rest_client, singular_arg, plural_arg):
-        kwargs = {
-            "content": "testing",
-            "attachment": undefined.UNDEFINED,
-            "attachments": undefined.UNDEFINED,
-            "component": undefined.UNDEFINED,
-            "components": undefined.UNDEFINED,
-            "embed": undefined.UNDEFINED,
-            "embeds": undefined.UNDEFINED,
-            "tts": undefined.UNDEFINED,
-            "mentions_everyone": undefined.UNDEFINED,
-            "mentions_reply": undefined.UNDEFINED,
-            "user_mentions": undefined.UNDEFINED,
-            "role_mentions": undefined.UNDEFINED,
-            # Keys will be replaced
-            plural_arg: object(),
-        }
-
+    def test__build_message_payload_when_non_collection_passed_to_plural(self, rest_client, singular_arg, plural_arg):
         expected_error_message = (
             f"You passed a non-collection to '{plural_arg}', but this expects a collection. Maybe you meant to use "
             f"'{singular_arg}' (singular) instead?"
         )
 
         with pytest.raises(TypeError, match=re.escape(expected_error_message)):
-            await rest_client._create_message(None, {}, **kwargs)
+            rest_client._build_message_payload(**{plural_arg: object()})
 
-    @pytest.mark.skip("TODO")
-    async def test__create_message(self, rest_client):
-        ...
+    async def test_create_message_when_form(self, rest_client):
+        attachment_obj = object()
+        attachment_obj2 = object()
+        component_obj = object()
+        component_obj2 = object()
+        embed_obj = object()
+        embed_obj2 = object()
+        mock_form = mock.Mock()
+        mock_body = data_binding.JSONObjectBuilder()
+        mock_body.put("testing", "ensure_in_test")
+        expected_route = routes.POST_CHANNEL_MESSAGES.compile(channel=123456789)
+        rest_client._build_message_payload = mock.Mock(return_value=(mock_body, mock_form))
+        rest_client._request = mock.AsyncMock(return_value={"message_id": 123})
 
-    async def test_create_message(self, rest_client):
-        mock_object = object()
+        returned = await rest_client.create_message(
+            StubModel(123456789),
+            content="new content",
+            attachment=attachment_obj,
+            attachments=[attachment_obj2],
+            component=component_obj,
+            components=[component_obj2],
+            embed=embed_obj,
+            embeds=[embed_obj2],
+            tts=True,
+            mentions_everyone=False,
+            user_mentions=[9876],
+            role_mentions=[1234],
+            nonce="noncing",
+            reply=StubModel(987654321),
+        )
+        assert returned is rest_client._entity_factory.deserialize_message.return_value
+
+        rest_client._build_message_payload.assert_called_once_with(
+            content="new content",
+            attachment=attachment_obj,
+            attachments=[attachment_obj2],
+            component=component_obj,
+            components=[component_obj2],
+            embed=embed_obj,
+            embeds=[embed_obj2],
+            tts=True,
+            mentions_everyone=False,
+            mentions_reply=undefined.UNDEFINED,
+            user_mentions=[9876],
+            role_mentions=[1234],
+        )
+        mock_form.add_field.assert_called_once_with(
+            "payload_json",
+            '{"testing": "ensure_in_test", "nonce": "noncing", "message_reference": {"message_id": "987654321"}}',
+            content_type="application/json",
+        )
+        rest_client._request.assert_awaited_once_with(expected_route, form_builder=mock_form)
+        rest_client._entity_factory.deserialize_message.assert_called_once_with({"message_id": 123})
+
+    async def test_create_message_when_no_form(self, rest_client):
         attachment_obj = object()
         attachment_obj2 = object()
         component_obj = object()
         component_obj2 = object()
         embed_obj = object()
         embed_obj2 = object()
+        mock_body = data_binding.JSONObjectBuilder()
+        mock_body.put("testing", "ensure_in_test")
         expected_route = routes.POST_CHANNEL_MESSAGES.compile(channel=123456789)
-        rest_client._create_message = mock.AsyncMock(return_value=mock_object)
+        rest_client._build_message_payload = mock.Mock(return_value=(mock_body, None))
+        rest_client._request = mock.AsyncMock(return_value={"message_id": 123})
 
         returned = await rest_client.create_message(
             StubModel(123456789),
@@ -1983,11 +2258,9 @@ async def test_create_message(self, rest_client):
             nonce="noncing",
             reply=StubModel(987654321),
         )
-        assert returned is mock_object
+        assert returned is rest_client._entity_factory.deserialize_message.return_value
 
-        rest_client._create_message.assert_awaited_once_with(
-            expected_route,
-            {"nonce": "noncing", "message_reference": {"message_id": "987654321"}},
+        rest_client._build_message_payload.assert_called_once_with(
             content="new content",
             attachment=attachment_obj,
             attachments=[attachment_obj2],
@@ -2001,6 +2274,11 @@ async def test_create_message(self, rest_client):
             user_mentions=[9876],
             role_mentions=[1234],
         )
+        rest_client._request.assert_awaited_once_with(
+            expected_route,
+            json={"testing": "ensure_in_test", "nonce": "noncing", "message_reference": {"message_id": "987654321"}},
+        )
+        rest_client._entity_factory.deserialize_message.assert_called_once_with({"message_id": 123})
 
     async def test_crosspost_message(self, rest_client):
         expected_route = routes.POST_CHANNEL_CROSSPOST.compile(channel=444432, message=12353234)
@@ -2016,78 +2294,72 @@ async def test_crosspost_message(self, rest_client):
         )
         rest_client._request.assert_awaited_once_with(expected_route)
 
-    @pytest.mark.parametrize(
-        ("singular_arg", "plural_arg"),
-        [("attachment", "attachments"), ("component", "components"), ("embed", "embeds")],
-    )
-    async def test__edit_message_when_both_single_and_plural_args_passed(self, rest_client, singular_arg, plural_arg):
-        kwargs = {
-            "content": "testing",
-            "attachment": undefined.UNDEFINED,
-            "attachments": undefined.UNDEFINED,
-            "component": undefined.UNDEFINED,
-            "components": undefined.UNDEFINED,
-            "embed": undefined.UNDEFINED,
-            "embeds": undefined.UNDEFINED,
-            "replace_attachments": True,
-            "mentions_everyone": undefined.UNDEFINED,
-            "mentions_reply": undefined.UNDEFINED,
-            "user_mentions": undefined.UNDEFINED,
-            "role_mentions": undefined.UNDEFINED,
-            # Keys will be replaced
-            singular_arg: object(),
-            plural_arg: object(),
-        }
-
-        with pytest.raises(
-            ValueError, match=rf"You may only specify one of '{singular_arg}' or '{plural_arg}', not both"
-        ):
-            await rest_client._edit_message(None, {}, **kwargs)
-
-    @pytest.mark.parametrize(
-        ("singular_arg", "plural_arg"),
-        [("attachment", "attachments"), ("component", "components"), ("embed", "embeds")],
-    )
-    async def test__edit_message_when_non_collection_passed_to_plural(self, rest_client, singular_arg, plural_arg):
-        kwargs = {
-            "content": "testing",
-            "attachment": undefined.UNDEFINED,
-            "attachments": undefined.UNDEFINED,
-            "component": undefined.UNDEFINED,
-            "components": undefined.UNDEFINED,
-            "embed": undefined.UNDEFINED,
-            "embeds": undefined.UNDEFINED,
-            "replace_attachments": True,
-            "mentions_everyone": undefined.UNDEFINED,
-            "mentions_reply": undefined.UNDEFINED,
-            "user_mentions": undefined.UNDEFINED,
-            "role_mentions": undefined.UNDEFINED,
-            # Keys will be replaced
-            plural_arg: object(),
-        }
+    async def test_edit_message_when_form(self, rest_client):
+        attachment_obj = object()
+        attachment_obj2 = object()
+        component_obj = object()
+        component_obj2 = object()
+        embed_obj = object()
+        embed_obj2 = object()
+        mock_form = mock.Mock()
+        mock_body = data_binding.JSONObjectBuilder()
+        mock_body.put("testing", "ensure_in_test")
+        expected_route = routes.PATCH_CHANNEL_MESSAGE.compile(channel=123456789, message=987654321)
+        rest_client._build_message_payload = mock.Mock(return_value=(mock_body, mock_form))
+        rest_client._request = mock.AsyncMock(return_value={"message_id": 123})
 
-        expected_error_message = (
-            f"You passed a non-collection to '{plural_arg}', but this expects a collection. Maybe you meant to use "
-            f"'{singular_arg}' (singular) instead?"
+        returned = await rest_client.edit_message(
+            StubModel(123456789),
+            StubModel(987654321),
+            content="new content",
+            attachment=attachment_obj,
+            attachments=[attachment_obj2],
+            component=component_obj,
+            components=[component_obj2],
+            embed=embed_obj,
+            embeds=[embed_obj2],
+            replace_attachments=True,
+            mentions_everyone=False,
+            user_mentions=[9876],
+            role_mentions=[1234],
+            flags=120,
         )
+        assert returned is rest_client._entity_factory.deserialize_message.return_value
 
-        with pytest.raises(TypeError, match=re.escape(expected_error_message)):
-            await rest_client._edit_message(None, {}, **kwargs)
-
-    @pytest.mark.skip("TODO")
-    async def test__edit_message(self, rest_client):
-        ...
+        rest_client._build_message_payload.assert_called_once_with(
+            content="new content",
+            attachment=attachment_obj,
+            attachments=[attachment_obj2],
+            component=component_obj,
+            components=[component_obj2],
+            embed=embed_obj,
+            embeds=[embed_obj2],
+            flags=120,
+            replace_attachments=True,
+            mentions_everyone=False,
+            mentions_reply=undefined.UNDEFINED,
+            user_mentions=[9876],
+            role_mentions=[1234],
+            edit=True,
+        )
+        mock_form.add_field.assert_called_once_with(
+            "payload_json", '{"testing": "ensure_in_test"}', content_type="application/json"
+        )
+        rest_client._request.assert_awaited_once_with(expected_route, form_builder=mock_form)
+        rest_client._entity_factory.deserialize_message.assert_called_once_with({"message_id": 123})
 
-    async def test_edit_message(self, rest_client):
-        mock_object = object()
+    async def test_edit_message_when_no_form(self, rest_client):
         attachment_obj = object()
         attachment_obj2 = object()
         component_obj = object()
         component_obj2 = object()
         embed_obj = object()
         embed_obj2 = object()
+        mock_body = data_binding.JSONObjectBuilder()
+        mock_body.put("testing", "ensure_in_test")
         expected_route = routes.PATCH_CHANNEL_MESSAGE.compile(channel=123456789, message=987654321)
-        rest_client._edit_message = mock.AsyncMock(return_value=mock_object)
+        rest_client._build_message_payload = mock.Mock(return_value=(mock_body, None))
+        rest_client._request = mock.AsyncMock(return_value={"message_id": 123})
 
         returned = await rest_client.edit_message(
             StubModel(123456789),
@@ -2105,11 +2377,9 @@ async def test_edit_message(self, rest_client):
             role_mentions=[1234],
             flags=120,
         )
-        assert returned is mock_object
+        assert returned is rest_client._entity_factory.deserialize_message.return_value
 
-        rest_client._edit_message.assert_awaited_once_with(
-            expected_route,
-            {"flags": 120},
+        rest_client._build_message_payload.assert_called_once_with(
             content="new content",
             attachment=attachment_obj,
             attachments=[attachment_obj2],
@@ -2117,12 +2387,16 @@ async def test_edit_message(self, rest_client):
             components=[component_obj2],
             embed=embed_obj,
             embeds=[embed_obj2],
+            flags=120,
             replace_attachments=True,
             mentions_everyone=False,
             mentions_reply=undefined.UNDEFINED,
             user_mentions=[9876],
             role_mentions=[1234],
+            edit=True,
         )
+        rest_client._request.assert_awaited_once_with(expected_route, json={"testing": "ensure_in_test"})
+        rest_client._entity_factory.deserialize_message.assert_called_once_with({"message_id": 123})
 
     async def test_follow_channel(self, rest_client):
         expected_route = routes.POST_CHANNEL_FOLLOWERS.compile(channel=3333)
@@ -2440,22 +2714,25 @@ async def test_delete_webhook_without_token(self, rest_client):
             (432, "https://website.com/davfsa_logo"),
         ],
     )
-    async def test_execute_webhook(self, rest_client, webhook, avatar_url):
-        mock_object = object()
+    async def test_execute_webhook_when_form(self, rest_client, webhook, avatar_url):
         attachment_obj = object()
         attachment_obj2 = object()
         component_obj = object()
         component_obj2 = object()
         embed_obj = object()
         embed_obj2 = object()
+        mock_form = mock.Mock()
+        mock_body = data_binding.JSONObjectBuilder()
+        mock_body.put("testing", "ensure_in_test")
         expected_route = routes.POST_WEBHOOK_WITH_TOKEN.compile(webhook=432, token="hi, im a token")
-        rest_client._create_message = mock.AsyncMock(return_value=mock_object)
+        rest_client._build_message_payload = mock.Mock(return_value=(mock_body, mock_form))
+        rest_client._request = mock.AsyncMock(return_value={"message_id": 123})
 
         returned = await rest_client.execute_webhook(
-            webhook,
+            432,
             "hi, im a token",
             username="davfsa",
-            avatar_url=avatar_url,
+            avatar_url="https://website.com/davfsa_logo",
             content="new content",
             attachment=attachment_obj,
             attachments=[attachment_obj2],
@@ -2469,13 +2746,53 @@ async def test_execute_webhook(self, rest_client, webhook, avatar_url):
             role_mentions=[1234],
             flags=120,
         )
-        assert returned is mock_object
+        assert returned is rest_client._entity_factory.deserialize_message.return_value
 
-        rest_client._create_message.assert_awaited_once_with(
+        rest_client._build_message_payload.assert_called_once_with(
+            content="new content",
+            attachment=attachment_obj,
+            attachments=[attachment_obj2],
+            component=component_obj,
+            components=[component_obj2],
+            embed=embed_obj,
+            embeds=[embed_obj2],
+            tts=True,
+            flags=120,
+            mentions_everyone=False,
+            user_mentions=[9876],
+            role_mentions=[1234],
+        )
+        mock_form.add_field.assert_called_once_with(
+            "payload_json",
+            '{"testing": "ensure_in_test", "username": "davfsa", "avatar_url": "https://website.com/davfsa_logo"}',
+            content_type="application/json",
+        )
+        rest_client._request.assert_awaited_once_with(
             expected_route,
-            {"username": "davfsa", "avatar_url": "https://website.com/davfsa_logo", "flags": 120},
-            {"wait": "true"},
+            form_builder=mock_form,
+            query={"wait": "true"},
             no_auth=True,
+        )
+        rest_client._entity_factory.deserialize_message.assert_called_once_with({"message_id": 123})
+
+    async def test_execute_webhook_when_no_form(self, rest_client):
+        attachment_obj = object()
+        attachment_obj2 = object()
+        component_obj = object()
+        component_obj2 = object()
+        embed_obj = object()
+        embed_obj2 = object()
+        mock_body = data_binding.JSONObjectBuilder()
+        mock_body.put("testing", "ensure_in_test")
+        expected_route = routes.POST_WEBHOOK_WITH_TOKEN.compile(webhook=432, token="hi, im a token")
+        rest_client._build_message_payload = mock.Mock(return_value=(mock_body, None))
+        rest_client._request = mock.AsyncMock(return_value={"message_id": 123})
+
+        returned = await rest_client.execute_webhook(
+            432,
+            "hi, im a token",
+            username="davfsa",
+            avatar_url="https://website.com/davfsa_logo",
             content="new content",
             attachment=attachment_obj,
             attachments=[attachment_obj2],
@@ -2485,10 +2802,33 @@ async def test_execute_webhook(self, rest_client, webhook, avatar_url):
             embeds=[embed_obj2],
             tts=True,
             mentions_everyone=False,
-            mentions_reply=undefined.UNDEFINED,
+            user_mentions=[9876],
+            role_mentions=[1234],
+            flags=120,
+        )
+        assert returned is rest_client._entity_factory.deserialize_message.return_value
+
+        rest_client._build_message_payload.assert_called_once_with(
+            content="new content",
+            attachment=attachment_obj,
+            attachments=[attachment_obj2],
+            component=component_obj,
+            components=[component_obj2],
+            embed=embed_obj,
+            embeds=[embed_obj2],
+            tts=True,
+            flags=120,
+            mentions_everyone=False,
             user_mentions=[9876],
             role_mentions=[1234],
         )
+        rest_client._request.assert_awaited_once_with(
+            expected_route,
+            json={"testing": "ensure_in_test", "username": "davfsa", "avatar_url": "https://website.com/davfsa_logo"},
+            query={"wait": "true"},
+            no_auth=True,
+        )
+        rest_client._entity_factory.deserialize_message.assert_called_once_with({"message_id": 123})
 
     @pytest.mark.parametrize("webhook", [mock.Mock(webhooks.ExecutableWebhook, webhook_id=432), 432])
     async def test_fetch_webhook_message(self, rest_client, webhook):
@@ -2503,16 +2843,19 @@ async def test_fetch_webhook_message(self, rest_client, webhook):
         rest_client._entity_factory.deserialize_message.assert_called_once_with({"id": "456"})
 
     @pytest.mark.parametrize("webhook", [mock.Mock(webhooks.ExecutableWebhook, webhook_id=432), 432])
-    async def test_edit_webhook_message(self, rest_client, webhook):
-        mock_object = object()
+    async def test_edit_webhook_message_when_form(self, rest_client, webhook):
         attachment_obj = object()
         attachment_obj2 = object()
         component_obj = object()
         component_obj2 = object()
         embed_obj = object()
         embed_obj2 = object()
+        mock_form = mock.Mock()
+        mock_body = data_binding.JSONObjectBuilder()
+        mock_body.put("testing", "ensure_in_test")
         expected_route = routes.PATCH_WEBHOOK_MESSAGE.compile(webhook=432, token="hi, im a token", message=456)
-        rest_client._edit_message = mock.AsyncMock(return_value=mock_object)
+        rest_client._build_message_payload = mock.Mock(return_value=(mock_body, mock_form))
+        rest_client._request = mock.AsyncMock(return_value={"message_id": 123})
 
         returned = await rest_client.edit_webhook_message(
             webhook,
@@ -2530,12 +2873,9 @@ async def test_edit_webhook_message(self, rest_client, webhook):
             user_mentions=[9876],
             role_mentions=[1234],
         )
-        assert returned is mock_object
+        assert returned is rest_client._entity_factory.deserialize_message.return_value
 
-        rest_client._edit_message.assert_awaited_once_with(
-            expected_route,
-            {},
-            no_auth=True,
+        rest_client._build_message_payload.assert_called_once_with(
             content="new content",
             attachment=attachment_obj,
             attachments=[attachment_obj2],
@@ -2545,10 +2885,63 @@ async def test_edit_webhook_message(self, rest_client, webhook):
             embeds=[embed_obj2],
             replace_attachments=True,
             mentions_everyone=False,
-            mentions_reply=undefined.UNDEFINED,
             user_mentions=[9876],
             role_mentions=[1234],
+            edit=True,
+        )
+        mock_form.add_field.assert_called_once_with(
+            "payload_json", '{"testing": "ensure_in_test"}', content_type="application/json"
         )
+        rest_client._request.assert_awaited_once_with(expected_route, form_builder=mock_form, no_auth=True)
+        rest_client._entity_factory.deserialize_message.assert_called_once_with({"message_id": 123})
+
+    async def test_edit_webhook_message_when_no_form(self, rest_client):
+        attachment_obj = object()
+        attachment_obj2 = object()
+        component_obj = object()
+        component_obj2 = object()
+        embed_obj = object()
+        embed_obj2 = object()
+        mock_body = data_binding.JSONObjectBuilder()
+        mock_body.put("testing", "ensure_in_test")
+        expected_route = routes.PATCH_WEBHOOK_MESSAGE.compile(webhook=432, token="hi, im a token", message=456)
+        rest_client._build_message_payload = mock.Mock(return_value=(mock_body, None))
+        rest_client._request = mock.AsyncMock(return_value={"message_id": 123})
+
+        returned = await rest_client.edit_webhook_message(
+            432,
+            "hi, im a token",
+            StubModel(456),
+            content="new content",
+            attachment=attachment_obj,
+            attachments=[attachment_obj2],
+            component=component_obj,
+            components=[component_obj2],
+            embed=embed_obj,
+            embeds=[embed_obj2],
+            replace_attachments=True,
+            mentions_everyone=False,
+            user_mentions=[9876],
+            role_mentions=[1234],
+        )
+        assert returned is rest_client._entity_factory.deserialize_message.return_value
+
+        rest_client._build_message_payload.assert_called_once_with(
+            content="new content",
+            attachment=attachment_obj,
+            attachments=[attachment_obj2],
+            component=component_obj,
+            components=[component_obj2],
+            embed=embed_obj,
+            embeds=[embed_obj2],
+            replace_attachments=True,
+            mentions_everyone=False,
+            user_mentions=[9876],
+            role_mentions=[1234],
+            edit=True,
+        )
+        rest_client._request.assert_awaited_once_with(expected_route, json={"testing": "ensure_in_test"}, no_auth=True)
+        rest_client._entity_factory.deserialize_message.assert_called_once_with({"message_id": 123})
 
     @pytest.mark.parametrize("webhook", [mock.Mock(webhooks.ExecutableWebhook, webhook_id=123), 123])
     async def test_delete_webhook_message(self, rest_client, webhook):
@@ -3466,7 +3859,7 @@ async def test_fetch_member(self, rest_client):
         rest_client._request.assert_awaited_once_with(expected_route)
         rest_client._entity_factory.deserialize_member.assert_called_once_with({"id": "789"}, guild_id=123)
 
-    async def test_fetch_my_member(self, rest_client) -> guilds.Member:
+    async def test_fetch_my_member(self, rest_client):
         expected_route = routes.GET_MY_GUILD_MEMBER.compile(guild=45123)
         rest_client._request = mock.AsyncMock(return_value={"id": "595995"})
 
@@ -4456,24 +4849,123 @@ async def test_fetch_interaction_response(self, rest_client):
         rest_client._entity_factory.deserialize_message.assert_called_once_with(rest_client._request.return_value)
         rest_client._request.assert_awaited_once_with(expected_route, no_auth=True)
 
-    @pytest.mark.skip("TODO")
-    async def test_create_interaction_response(self, rest_client):
-        ...
+    async def test_create_interaction_response_when_form(self, rest_client):
+        attachment_obj = object()
+        attachment_obj2 = object()
+        component_obj = object()
+        component_obj2 = object()
+        embed_obj = object()
+        embed_obj2 = object()
+        mock_form = mock.Mock()
+        mock_body = data_binding.JSONObjectBuilder()
+        mock_body.put("testing", "ensure_in_test")
+        expected_route = routes.POST_INTERACTION_RESPONSE.compile(interaction=432, token="some token")
+        rest_client._build_message_payload = mock.Mock(return_value=(mock_body, mock_form))
+        rest_client._request = mock.AsyncMock()
 
-    async def test_edit_interaction_response(self, rest_client):
-        mock_object = object()
+        await rest_client.create_interaction_response(
+            StubModel(432),
+            "some token",
+            1,
+            "some content",
+            attachment=attachment_obj,
+            attachments=[attachment_obj2],
+            component=component_obj,
+            components=[component_obj2],
+            embed=embed_obj,
+            embeds=[embed_obj2],
+            tts=True,
+            flags=120,
+            mentions_everyone=False,
+            user_mentions=[9876],
+            role_mentions=[1234],
+        )
+
+        rest_client._build_message_payload.assert_called_once_with(
+            content="some content",
+            attachment=attachment_obj,
+            attachments=[attachment_obj2],
+            component=component_obj,
+            components=[component_obj2],
+            embed=embed_obj,
+            embeds=[embed_obj2],
+            tts=True,
+            flags=120,
+            mentions_everyone=False,
+            user_mentions=[9876],
+            role_mentions=[1234],
+        )
+        mock_form.add_field.assert_called_once_with(
+            "payload_json", '{"type": 1, "data": {"testing": "ensure_in_test"}}', content_type="application/json"
+        )
+        rest_client._request.assert_awaited_once_with(expected_route, form_builder=mock_form, no_auth=True)
+
+    async def test_create_interaction_response_when_no_form(self, rest_client):
+        attachment_obj = object()
+        attachment_obj2 = object()
+        component_obj = object()
+        component_obj2 = object()
+        embed_obj = object()
+        embed_obj2 = object()
+        mock_body = data_binding.JSONObjectBuilder()
+        mock_body.put("testing", "ensure_in_test")
+        expected_route = routes.POST_INTERACTION_RESPONSE.compile(interaction=432, token="some token")
+        rest_client._build_message_payload = mock.Mock(return_value=(mock_body, None))
+        rest_client._request = mock.AsyncMock()
+
+        await rest_client.create_interaction_response(
+            StubModel(432),
+            "some token",
+            1,
+            "some content",
+            attachment=attachment_obj,
+            attachments=[attachment_obj2],
+            component=component_obj,
+            components=[component_obj2],
+            embed=embed_obj,
+            embeds=[embed_obj2],
+            tts=True,
+            flags=120,
+            mentions_everyone=False,
+            user_mentions=[9876],
+            role_mentions=[1234],
+        )
+
+        rest_client._build_message_payload.assert_called_once_with(
+            content="some content",
+            attachment=attachment_obj,
+            attachments=[attachment_obj2],
+            component=component_obj,
+            components=[component_obj2],
+            embed=embed_obj,
+            embeds=[embed_obj2],
+            tts=True,
+            flags=120,
+            mentions_everyone=False,
+            user_mentions=[9876],
+            role_mentions=[1234],
+        )
+        rest_client._request.assert_awaited_once_with(
+            expected_route, json={"type": 1, "data": {"testing": "ensure_in_test"}}, no_auth=True
+        )
+
+    async def test_edit_interaction_response_when_form(self, rest_client):
         attachment_obj = object()
         attachment_obj2 = object()
         component_obj = object()
         component_obj2 = object()
         embed_obj = object()
         embed_obj2 = object()
-        expected_route = routes.PATCH_INTERACTION_RESPONSE.compile(webhook=432, token="hi, im a token")
-        rest_client._edit_message = mock.AsyncMock(return_value=mock_object)
+        mock_form = mock.Mock()
+        mock_body = data_binding.JSONObjectBuilder()
+        mock_body.put("testing", "ensure_in_test")
+        expected_route = routes.PATCH_INTERACTION_RESPONSE.compile(webhook=432, token="some token")
+        rest_client._build_message_payload = mock.Mock(return_value=(mock_body, mock_form))
+        rest_client._request = mock.AsyncMock(return_value={"message_id": 123})
 
         returned = await rest_client.edit_interaction_response(
             StubModel(432),
-            "hi, im a token",
+            "some token",
             content="new content",
             attachment=attachment_obj,
             attachments=[attachment_obj2],
@@ -4486,12 +4978,59 @@ async def test_edit_interaction_response(self, rest_client):
             user_mentions=[9876],
             role_mentions=[1234],
         )
-        assert returned is mock_object
+        assert returned is rest_client._entity_factory.deserialize_message.return_value
 
-        rest_client._edit_message.assert_awaited_once_with(
-            expected_route,
-            {},
-            no_auth=True,
+        rest_client._build_message_payload.assert_called_once_with(
+            content="new content",
+            attachment=attachment_obj,
+            attachments=[attachment_obj2],
+            component=component_obj,
+            components=[component_obj2],
+            embed=embed_obj,
+            embeds=[embed_obj2],
+            replace_attachments=True,
+            mentions_everyone=False,
+            user_mentions=[9876],
+            role_mentions=[1234],
+            edit=True,
+        )
+        mock_form.add_field.assert_called_once_with(
+            "payload_json", '{"testing": "ensure_in_test"}', content_type="application/json"
+        )
+        rest_client._request.assert_awaited_once_with(expected_route, form_builder=mock_form, no_auth=True)
+        rest_client._entity_factory.deserialize_message.assert_called_once_with({"message_id": 123})
+
+    async def test_edit_interaction_response_when_no_form(self, rest_client):
+        attachment_obj = object()
+        attachment_obj2 = object()
+        component_obj = object()
+        component_obj2 = object()
+        embed_obj = object()
+        embed_obj2 = object()
+        mock_body = data_binding.JSONObjectBuilder()
+        mock_body.put("testing", "ensure_in_test")
+        expected_route = routes.PATCH_INTERACTION_RESPONSE.compile(webhook=432, token="some token")
+        rest_client._build_message_payload = mock.Mock(return_value=(mock_body, None))
+        rest_client._request = mock.AsyncMock(return_value={"message_id": 123})
+
+        returned = await rest_client.edit_interaction_response(
+            StubModel(432),
+            "some token",
+            content="new content",
+            attachment=attachment_obj,
+            attachments=[attachment_obj2],
+            component=component_obj,
+            components=[component_obj2],
+            embed=embed_obj,
+            embeds=[embed_obj2],
+            replace_attachments=True,
+            mentions_everyone=False,
+            user_mentions=[9876],
+            role_mentions=[1234],
+        )
+        assert returned is rest_client._entity_factory.deserialize_message.return_value
+
+        rest_client._build_message_payload.assert_called_once_with(
             content="new content",
             attachment=attachment_obj,
             attachments=[attachment_obj2],
@@ -4501,10 +5040,12 @@ async def test_edit_interaction_response(self, rest_client):
             embeds=[embed_obj2],
             replace_attachments=True,
             mentions_everyone=False,
-            mentions_reply=undefined.UNDEFINED,
             user_mentions=[9876],
             role_mentions=[1234],
+            edit=True,
         )
+        rest_client._request.assert_awaited_once_with(expected_route, json={"testing": "ensure_in_test"}, no_auth=True)
+        rest_client._entity_factory.deserialize_message.assert_called_once_with({"message_id": 123})
 
     async def test_delete_interaction_response(self, rest_client):
         expected_route = routes.DELETE_INTERACTION_RESPONSE.compile(webhook=1235431, token="go homo now")
diff --git a/tests/hikari/impl/test_rest_bot.py b/tests/hikari/impl/test_rest_bot.py
index 25f7ae3b..cee02a7c 100644
--- a/tests/hikari/impl/test_rest_bot.py
+++ b/tests/hikari/impl/test_rest_bot.py
@@ -168,6 +168,31 @@ def test___init___parses_string_public_key(self):
                 entity_factory=result.entity_factory, public_key=b"ofdododoo", rest_client=result.rest
             )
 
+    def test___init___strips_token(self):
+        cls = hikari_test_helpers.mock_class_namespace(rest_bot_impl.RESTBot, print_banner=mock.Mock())
+
+        stack = contextlib.ExitStack()
+        stack.enter_context(mock.patch.object(ux, "init_logging"))
+        rest_client = stack.enter_context(mock.patch.object(rest_impl, "RESTClientImpl"))
+        http_settings = stack.enter_context(mock.patch.object(config, "HTTPSettings"))
+        proxy_settings = stack.enter_context(mock.patch.object(config, "ProxySettings"))
+
+        with stack:
+            result = cls("\n\r sddsa tokenoken \n", "token_type")
+
+        rest_client.assert_called_once_with(
+            cache=None,
+            entity_factory=result.entity_factory,
+            executor=None,
+            http_settings=http_settings.return_value,
+            max_rate_limit=300.0,
+            max_retries=3,
+            proxy_settings=proxy_settings.return_value,
+            rest_url=None,
+            token="sddsa tokenoken",
+            token_type="token_type",
+        )
+
     def test___init___generates_default_settings(self):
         cls = hikari_test_helpers.mock_class_namespace(rest_bot_impl.RESTBot, print_banner=mock.Mock())
         stack = contextlib.ExitStack()
diff --git a/tests/hikari/interactions/test_base_interactions.py b/tests/hikari/interactions/test_base_interactions.py
index 69ea6eee..16f92862 100644
--- a/tests/hikari/interactions/test_base_interactions.py
+++ b/tests/hikari/interactions/test_base_interactions.py
@@ -74,6 +74,8 @@ async def test_create_initial_response_with_optional_args(self, mock_message_res
         mock_embed_2 = object()
         mock_component = object()
         mock_components = object(), object()
+        mock_attachment = object()
+        mock_attachments = object(), object()
         await mock_message_response_mixin.create_initial_response(
             base_interactions.ResponseType.MESSAGE_CREATE,
             "content",
@@ -83,6 +85,8 @@ async def test_create_initial_response_with_optional_args(self, mock_message_res
             embeds=[mock_embed_2],
             component=mock_component,
             components=mock_components,
+            attachment=mock_attachment,
+            attachments=mock_attachments,
             mentions_everyone=False,
             user_mentions=[123432],
             role_mentions=[6324523],
@@ -99,6 +103,8 @@ async def test_create_initial_response_with_optional_args(self, mock_message_res
             embeds=[mock_embed_2],
             component=mock_component,
             components=mock_components,
+            attachment=mock_attachment,
+            attachments=mock_attachments,
             mentions_everyone=False,
             user_mentions=[123432],
             role_mentions=[6324523],
@@ -121,6 +127,8 @@ async def test_create_initial_response_without_optional_args(self, mock_message_
             embeds=undefined.UNDEFINED,
             component=undefined.UNDEFINED,
             components=undefined.UNDEFINED,
+            attachment=undefined.UNDEFINED,
+            attachments=undefined.UNDEFINED,
             mentions_everyone=undefined.UNDEFINED,
             user_mentions=undefined.UNDEFINED,
             role_mentions=undefined.UNDEFINED,
diff --git a/tests/hikari/test_presences.py b/tests/hikari/test_presences.py
index 1af55581..fd1c0393 100644
--- a/tests/hikari/test_presences.py
+++ b/tests/hikari/test_presences.py
@@ -23,9 +23,12 @@
 import mock
 import pytest
 
+from hikari import files
 from hikari import presences
 from hikari import snowflakes
+from hikari import urls
 from hikari.impl import bot
+from hikari.internal import routes
 
 
 @pytest.fixture()
@@ -33,9 +36,188 @@ def mock_app():
     return mock.Mock(spec_set=bot.GatewayBot)
 
 
-def test_Activity_str_operator():
-    activity = presences.Activity(name="something", type=presences.ActivityType(1))
-    assert str(activity) == "something"
+class TestActivityAssets:
+    def test_large_image_url_property(self):
+        asset = presences.ActivityAssets(
+            application_id=None,
+            large_image=None,
+            large_text=None,
+            small_image=None,
+            small_text=None,
+        )
+
+        with mock.patch.object(presences.ActivityAssets, "make_large_image_url") as make_large_image_url:
+            result = asset.large_image_url
+
+        assert result is make_large_image_url.return_value
+        make_large_image_url.assert_called_once_with()
+
+    def test_large_image_url_property_when_runtime_error(self):
+        asset = presences.ActivityAssets(
+            application_id=None,
+            large_image=None,
+            large_text=None,
+            small_image=None,
+            small_text=None,
+        )
+
+        with mock.patch.object(
+            presences.ActivityAssets, "make_large_image_url", side_effect=RuntimeError
+        ) as make_large_image_url:
+            result = asset.large_image_url
+
+        assert result is None
+        make_large_image_url.assert_called_once_with()
+
+    def test_make_large_image_url(self):
+        asset = presences.ActivityAssets(
+            application_id=45123123,
+            large_image="541sdfasdasd",
+            large_text=None,
+            small_image=None,
+            small_text=None,
+        )
+
+        with mock.patch.object(routes, "CDN_APPLICATION_ASSET") as route:
+            assert asset.make_large_image_url(ext="fa", size=3121) is route.compile_to_file.return_value
+
+        route.compile_to_file.assert_called_once_with(
+            urls.CDN_URL,
+            application_id=45123123,
+            hash="541sdfasdasd",
+            size=3121,
+            file_format="fa",
+        )
+
+    def test_make_large_image_url_when_no_hash(self):
+        asset = presences.ActivityAssets(
+            application_id=None,
+            large_image=None,
+            large_text=None,
+            small_image=None,
+            small_text=None,
+        )
+
+        assert asset.make_large_image_url() is None
+
+    @pytest.mark.parametrize(
+        ("asset_hash", "expected"), [("mp:541sdfasdasd", "https://media.discordapp.net/541sdfasdasd")]
+    )
+    def test_make_large_image_url_when_dynamic_url(self, asset_hash: str, expected: str):
+        asset = presences.ActivityAssets(
+            application_id=None,
+            large_image=asset_hash,
+            large_text=None,
+            small_image=None,
+            small_text=None,
+        )
+
+        assert asset.make_large_image_url() == files.URL(expected)
+
+    def test_make_large_image_url_when_unknown_dynamic_url(self):
+        asset = presences.ActivityAssets(
+            application_id=None,
+            large_image="uwu:nou",
+            large_text=None,
+            small_image=None,
+            small_text=None,
+        )
+
+        with pytest.raises(RuntimeError, match="Unknown asset type"):
+            asset.make_large_image_url()
+
+    def test_small_image_url_property(self):
+        asset = presences.ActivityAssets(
+            application_id=None,
+            large_image=None,
+            large_text=None,
+            small_image=None,
+            small_text=None,
+        )
+
+        with mock.patch.object(presences.ActivityAssets, "make_small_image_url") as make_small_image_url:
+            result = asset.small_image_url
+
+        assert result is make_small_image_url.return_value
+        make_small_image_url.assert_called_once_with()
+
+    def test_small_image_url_property_when_runtime_error(self):
+        asset = presences.ActivityAssets(
+            application_id=None,
+            large_image=None,
+            large_text=None,
+            small_image=None,
+            small_text=None,
+        )
+
+        with mock.patch.object(
+            presences.ActivityAssets, "make_small_image_url", side_effect=RuntimeError
+        ) as make_small_image_url:
+            result = asset.small_image_url
+
+        assert result is None
+        make_small_image_url.assert_called_once_with()
+
+    def test_make_small_image_url(self):
+        asset = presences.ActivityAssets(
+            application_id=123321,
+            large_image=None,
+            large_text=None,
+            small_image="aseqwsdas",
+            small_text=None,
+        )
+
+        with mock.patch.object(routes, "CDN_APPLICATION_ASSET") as route:
+            assert asset.make_small_image_url(ext="eat", size=123312) is route.compile_to_file.return_value
+
+        route.compile_to_file.assert_called_once_with(
+            urls.CDN_URL,
+            application_id=123321,
+            hash="aseqwsdas",
+            size=123312,
+            file_format="eat",
+        )
+
+    def test_make_small_image_url_when_no_hash(self):
+        asset = presences.ActivityAssets(
+            application_id=None,
+            large_image=None,
+            large_text=None,
+            small_image=None,
+            small_text=None,
+        )
+
+        assert asset.make_small_image_url() is None
+
+    @pytest.mark.parametrize(("asset_hash", "expected"), [("mp:4123fdssdf", "https://media.discordapp.net/4123fdssdf")])
+    def test_make_small_image_url_when_dynamic_url(self, asset_hash: str, expected: str):
+        asset = presences.ActivityAssets(
+            application_id=None,
+            large_image=None,
+            large_text=None,
+            small_image=asset_hash,
+            small_text=None,
+        )
+
+        assert asset.make_small_image_url() == files.URL(expected)
+
+    def test_make_small_image_url_when_unknown_dynamic_url(self):
+        asset = presences.ActivityAssets(
+            application_id=None,
+            large_image=None,
+            large_text=None,
+            small_image="meow:nyaa",
+            small_text=None,
+        )
+
+        with pytest.raises(RuntimeError, match="Unknown asset type"):
+            asset.make_small_image_url()
+
+
+class TestActivity:
+    def test_str_operator(self):
+        activity = presences.Activity(name="something", type=presences.ActivityType(1))
+        assert str(activity) == "something"
 
 
 class TestMemberPresence: