Repository: hikari-py/hikari
Pull request #657 (2021-06-12 04:47:13)

Remove a couple type ignores + cleanup CacheMappingView
### Summary
I really wanted to remove a few of those pesky type ignores, you know? But I may have added more than I removed... Anyways, this PR also contains cache changes as when I asked around about whether I could change some interfaces, I got directed to remove some things instead. 

### Checklist
<!-- Make sure to tick all the following boxes by putting an `x` in between (like this `[x]`) -->
- [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
None.


A5rocks: This PR needs:

> First-time contributors need a maintainer to approve running workflows
A5rocks: Gonna close and re-open to give github actions the little nudge it needs I guess?
A5rocks: @FasterSpeeding you may want to review the latest commit, as it changes some of the cache things.

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 417f2c91..dde45dfc 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -139,7 +139,7 @@ jobs:
           python-version: 3.8
 
       - name: Setup node
-        uses: actions/setup-node@v2.3.1
+        uses: actions/setup-node@v2.3.2
 
       - name: Build pages
         run: |
diff --git a/README.md b/README.md
index 568e80e3..8a6daf3b 100644
--- a/README.md
+++ b/README.md
@@ -51,17 +51,17 @@ writing their first bot in other frameworks simply because of working blind afte
 to set up standard logging messages.
 
 If you wish to customise the intents being used in order to change which events your bot is notified about, then you
-can pass the `intents` kwarg to the `BotApp` constructor:
+can pass the `intents` kwarg to the `GatewayBot` constructor:
 
 ```py
 # the default is to enable all unprivileged intents (all events that do not target the
 # presence or activity of a specific member).
-bot = hikari.BotApp(intents=hikari.Intents.ALL, token="...")
+bot = hikari.GatewayBot(intents=hikari.Intents.ALL, token="...")
 ```
 
 The above example would enable all intents, thus enabling events relating to member presences to be received
 (you'd need to whitelist your application first to be able to start the bot if you do this).
-[Other options also exist](https://hikari-py.github.io/hikari/hikari/impl/bot.html#hikari.impl.bot.BotApp) such as
+[Other options also exist](https://hikari-py.github.io/hikari/hikari/impl/bot.html#hikari.impl.bot.GatewayBot) such as
 [customising timeouts for requests](https://hikari-py.github.io/hikari/hikari/config.html#hikari.config.HTTPSettings.timeouts)
 and [enabling a proxy](https://hikari-py.github.io/hikari/hikari/config.html#hikari.config.ProxySettings).
 
@@ -78,7 +78,7 @@ bot.run(
 )
 ```
 
-[Many other helpful options](https://hikari-py.github.io/hikari/hikari/impl/bot.html#hikari.impl.bot.BotApp.run)
+[Many other helpful options](https://hikari-py.github.io/hikari/hikari/impl/bot.html#hikari.impl.bot.GatewayBot.run)
 exist for you to take advantage of if you wish.
 
 Events are determined by the type annotation on the event parameter, or alternatively as a type passed to the
@@ -179,7 +179,7 @@ if os.name != "nt":
     import uvloop
     uvloop.install()
 
-bot = hikari.BotApp(...)
+bot = hikari.GatewayBot(...)
 ...
 ```
 
diff --git a/hikari/api/cache.py b/hikari/api/cache.py
index ac902731..fc037641 100644
--- a/hikari/api/cache.py
+++ b/hikari/api/cache.py
@@ -53,9 +53,19 @@ class CacheView(typing.Mapping[_KeyT, _ValueT], abc.ABC):
 
     __slots__: typing.Sequence[str] = ()
 
+    @typing.overload
     @abc.abstractmethod
-    def get_item_at(self, index: int) -> _ValueT:
-        """Get an entry in the view at position `index`."""
+    def get_item_at(self, index: int, /) -> _ValueT:
+        ...
+
+    @typing.overload
+    @abc.abstractmethod
+    def get_item_at(self, index: slice, /) -> typing.Sequence[_ValueT]:
+        ...
+
+    @abc.abstractmethod
+    def get_item_at(self, index: typing.Union[slice, int], /) -> typing.Union[_ValueT, typing.Sequence[_ValueT]]:
+        ...
 
     @abc.abstractmethod
     def iterator(self) -> iterators.LazyIterator[_ValueT]:
diff --git a/hikari/api/rest.py b/hikari/api/rest.py
index 6df5b36e..d1febb88 100644
--- a/hikari/api/rest.py
+++ b/hikari/api/rest.py
@@ -1552,7 +1552,8 @@ async def add_reaction(
         self,
         channel: snowflakes.SnowflakeishOr[channels_.TextableChannel],
         message: snowflakes.SnowflakeishOr[messages_.PartialMessage],
-        emoji: emojis.Emojiish,
+        emoji: typing.Union[str, emojis.Emoji],
+        emoji_id: undefined.UndefinedOr[snowflakes.SnowflakeishOr[emojis.CustomEmoji]] = undefined.UNDEFINED,
     ) -> None:
         """Add a reaction emoji to a message in a given channel.
 
@@ -1565,8 +1566,15 @@ async def add_reaction(
         message : hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage]
             The message to add a reaction to. This may be the
             object or the ID of an existing message.
-        emoji : hikari.emojis.Emojiish
-            The emoji to react to the message with.
+        emoji : typing.Union[builtins.str, hikari.emojis.Emoji]
+            Object or name of the emoji to react with.
+
+        Other Parameters
+        ----------------
+        emoji_id : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.emojis.CustomEmoji]]
+            ID of the custom emoji to react with.
+            This should only be provided when a custom emoji's name is passed
+            for `emoji`.
 
         Raises
         ------
@@ -1600,7 +1608,8 @@ async def delete_my_reaction(
         self,
         channel: snowflakes.SnowflakeishOr[channels_.TextableChannel],
         message: snowflakes.SnowflakeishOr[messages_.PartialMessage],
-        emoji: emojis.Emojiish,
+        emoji: typing.Union[str, emojis.Emoji],
+        emoji_id: undefined.UndefinedOr[snowflakes.SnowflakeishOr[emojis.CustomEmoji]] = undefined.UNDEFINED,
     ) -> None:
         """Delete a reaction that your application user created.
 
@@ -1612,8 +1621,15 @@ async def delete_my_reaction(
         message : hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage]
             The message to delete a reaction from. This may be the
             object or the ID of an existing message.
-        emoji : hikari.emojis.Emojiish
-            The emoji to remove your reaction from.
+        emoji : typing.Union[builtins.str, hikari.emojis.Emoji]
+            Object or name of the emoji to remove your reaction for.
+
+        Other Parameters
+        ----------------
+        emoji_id : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.emojis.CustomEmoji]]
+            ID of the custom emoji to remove your reaction for.
+            This should only be provided when a custom emoji's name is passed
+            for `emoji`.
 
         Raises
         ------
@@ -1644,7 +1660,8 @@ async def delete_all_reactions_for_emoji(
         self,
         channel: snowflakes.SnowflakeishOr[channels_.TextableChannel],
         message: snowflakes.SnowflakeishOr[messages_.PartialMessage],
-        emoji: emojis.Emojiish,
+        emoji: typing.Union[str, emojis.Emoji],
+        emoji_id: undefined.UndefinedOr[snowflakes.SnowflakeishOr[emojis.CustomEmoji]] = undefined.UNDEFINED,
     ) -> None:
         """Delete all reactions for a single emoji on a given message.
 
@@ -1656,8 +1673,15 @@ async def delete_all_reactions_for_emoji(
         message : hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage]
             The message to delete a reactions from. This may be the
             object or the ID of an existing message.
-        emoji : hikari.emojis.Emojiish
-            The emoji to delete all reactions from.
+        emoji : typing.Union[builtins.str, hikari.emojis.Emoji]
+            Object or name of the emoji to remove all the reactions for.
+
+        Other Parameters
+        ----------------
+        emoji_id : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.emojis.CustomEmoji]]
+            ID of the custom emoji to remove all the reactions for.
+            This should only be provided when a custom emoji's name is passed
+            for `emoji`.
 
         Raises
         ------
@@ -1690,8 +1714,9 @@ async def delete_reaction(
         self,
         channel: snowflakes.SnowflakeishOr[channels_.TextableChannel],
         message: snowflakes.SnowflakeishOr[messages_.PartialMessage],
-        emoji: emojis.Emojiish,
         user: snowflakes.SnowflakeishOr[users.PartialUser],
+        emoji: typing.Union[str, emojis.Emoji],
+        emoji_id: undefined.UndefinedOr[snowflakes.SnowflakeishOr[emojis.CustomEmoji]] = undefined.UNDEFINED,
     ) -> None:
         """Delete a reaction from a message.
 
@@ -1706,8 +1731,17 @@ async def delete_reaction(
         message : hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage]
             The message to delete a reaction from. This may be the
             object or the ID of an existing message.
-        emoji : hikari.emojis.Emojiish
-            The emoji to delete all reactions from.
+        user: hikari.snowflakes.SnowflakeishOr[hikari.users.PartialUser]
+            Object or ID of the user to remove the reaction of.
+        emoji : typing.Union[builtins.str, hikari.emojis.Emoji]
+            Object or name of the emoji to react with.
+
+        Other Parameters
+        ----------------
+        emoji_id : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.emojis.CustomEmoji]]
+            ID of the custom emoji to react with.
+            This should only be provided when a custom emoji's name is passed
+            for `emoji`.
 
         Raises
         ------
@@ -1783,7 +1817,8 @@ def fetch_reactions_for_emoji(
         self,
         channel: snowflakes.SnowflakeishOr[channels_.TextableChannel],
         message: snowflakes.SnowflakeishOr[messages_.PartialMessage],
-        emoji: emojis.Emojiish,
+        emoji: typing.Union[str, emojis.Emoji],
+        emoji_id: undefined.UndefinedOr[snowflakes.SnowflakeishOr[emojis.CustomEmoji]] = undefined.UNDEFINED,
     ) -> iterators.LazyIterator[users.User]:
         """Fetch reactions for an emoji from a message.
 
@@ -1795,8 +1830,15 @@ def fetch_reactions_for_emoji(
         message : hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage]
             The message to delete all reaction from. This may be the
             object or the ID of an existing message.
-        emoji : hikari.emojis.Emojiish
-            The emoji to filter reactions by.
+        emoji : typing.Union[builtins.str, hikari.emojis.Emoji]
+            Object or name of the emoji to get the reactions for.
+
+        Other Parameters
+        ----------------
+        emoji_id : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.emojis.CustomEmoji]]
+            ID of the custom emoji to get the reactions for.
+            This should only be provided when a custom emoji's name is passed
+            for `emoji`.
 
         Returns
         -------
diff --git a/hikari/config.py b/hikari/config.py
index c57d3269..351c6d78 100644
--- a/hikari/config.py
+++ b/hikari/config.py
@@ -330,55 +330,47 @@ def _(self, _: attr.Attribute[typing.Optional[int]], value: typing.Optional[int]
         if value is not None and (not isinstance(value, int) or value <= 0):
             raise ValueError("http_settings.max_redirects must be None or a POSITIVE integer")
 
-    _ssl: typing.Union[bool, ssl_.SSLContext] = attr.field(
-        default=True,
+    ssl: ssl_.SSLContext = attr.field(
+        factory=lambda: _ssl_factory(True),
         converter=_ssl_factory,
-        validator=attr.validators.instance_of(ssl_.SSLContext),  # type: ignore[assignment,arg-type]
+        validator=attr.validators.instance_of(ssl_.SSLContext),
     )
+    """SSL context to use.
 
-    @property
-    def ssl(self) -> ssl_.SSLContext:
-        """SSL context to use.
-
-        This may be __assigned__ a `builtins.bool` or an `ssl.SSLContext` object.
+    This may be __assigned__ a `builtins.bool` or an `ssl.SSLContext` object.
 
-        If assigned to `builtins.True`, a default SSL context is generated by
-        this class that will enforce SSL verification. This is then stored in
-        this field.
+    If assigned to `builtins.True`, a default SSL context is generated by
+    this class that will enforce SSL verification. This is then stored in
+    this field.
 
-        If `builtins.False`, then a default SSL context is generated by this
-        class that will **NOT** enforce SSL verification. This is then stored
-        in this field.
+    If `builtins.False`, then a default SSL context is generated by this
+    class that will **NOT** enforce SSL verification. This is then stored
+    in this field.
 
-        If an instance of `ssl.SSLContext`, then this context will be used.
+    If an instance of `ssl.SSLContext`, then this context will be used.
 
-        !!! warning
-            Setting a custom value here may have security implications, or
-            may result in the application being unable to connect to Discord
-            at all.
+    !!! warning
+        Setting a custom value here may have security implications, or
+        may result in the application being unable to connect to Discord
+        at all.
 
-        !!! warning
-            Disabling SSL verification is almost always unadvised. This
-            is because your application will no longer check whether you are
-            connecting to Discord, or to some third party spoof designed
-            to steal personal credentials such as your application token.
+    !!! warning
+        Disabling SSL verification is almost always unadvised. This
+        is because your application will no longer check whether you are
+        connecting to Discord, or to some third party spoof designed
+        to steal personal credentials such as your application token.
 
-            There may be cases where SSL certificates do not get updated,
-            and in this case, you may find that disabling this explicitly
-            allows you to work around any issues that are occurring, but
-            you should immediately seek a better solution where possible
-            if any form of personal security is in your interest.
+        There may be cases where SSL certificates do not get updated,
+        and in this case, you may find that disabling this explicitly
+        allows you to work around any issues that are occurring, but
+        you should immediately seek a better solution where possible
+        if any form of personal security is in your interest.
 
-        Returns
-        -------
-        ssl.SSLContext
-            The SSL context to use for this application.
-        """
-        ssl = self._ssl
-        assert isinstance(
-            ssl, ssl_.SSLContext
-        ), f"expected ssl.SSLContext, found {type(ssl)!r}. Did you overwrite the value?"
-        return ssl
+    Returns
+    -------
+    ssl.SSLContext
+        The SSL context to use for this application.
+    """
 
     timeouts: HTTPTimeoutSettings = attr.field(
         factory=HTTPTimeoutSettings, validator=attr.validators.instance_of(HTTPTimeoutSettings)
diff --git a/hikari/emojis.py b/hikari/emojis.py
index c8c1a534..3464893c 100644
--- a/hikari/emojis.py
+++ b/hikari/emojis.py
@@ -24,7 +24,7 @@
 
 from __future__ import annotations
 
-__all__: typing.List[str] = ["Emoji", "UnicodeEmoji", "CustomEmoji", "KnownCustomEmoji", "Emojiish"]
+__all__: typing.List[str] = ["Emoji", "UnicodeEmoji", "CustomEmoji", "KnownCustomEmoji"]
 
 import abc
 import re
@@ -62,7 +62,7 @@ class Emoji(files.WebResource, abc.ABC):
 
     @property
     @abc.abstractmethod
-    def name(self) -> typing.Optional[str]:
+    def name(self) -> str:
         """Return the generic name/representation for this emoji."""
 
     @property
@@ -75,11 +75,6 @@ def url(self) -> str:
     def url_name(self) -> str:
         """Name of the part of the emoji to use in requests."""
 
-    @property
-    def is_mentionable(self) -> bool:
-        """Whether the emoji can be mentioned or not."""
-        return True
-
     @property
     @abc.abstractmethod
     def mention(self) -> str:
@@ -98,21 +93,19 @@ def parse(cls, string: str, /) -> Emoji:
         -------
         Emoji
             The parsed emoji object. This will be a `CustomEmoji` if a custom
-            emoji ID or mention, or a `UnicodeEmoji` otherwise.
+            emoji mention, or a `UnicodeEmoji` otherwise.
 
         Raises
         ------
         builtins.ValueError
             If a mention is given that has an invalid format.
         """
-        if string.isdigit() or string.startswith("<") and string.endswith(">"):
+        if string.startswith("<") and string.endswith(">"):
             return CustomEmoji.parse(string)
         return UnicodeEmoji.parse(string)
 
 
-@attr_extensions.with_copy
-@attr.define(hash=True, weakref_slot=False)
-class UnicodeEmoji(Emoji):
+class UnicodeEmoji(str, Emoji):
     """Represents a unicode emoji.
 
     !!! warning
@@ -131,33 +124,27 @@ class UnicodeEmoji(Emoji):
         removed in a future release after a deprecation period.
     """
 
-    name: str = attr.field(repr=True, hash=True)
-    """The code points that form the emoji."""
-
-    def __str__(self) -> str:
-        return self.name
+    __slots__: typing.Sequence[str] = ()
 
-    def __eq__(self, other: typing.Any) -> bool:
-        if isinstance(other, Emoji):
-            return self.name == other.name
-        if isinstance(other, str):
-            return self.name == other
-        return False
+    @property
+    def name(self) -> str:
+        """Return the code points which form the emoji."""
+        return self
 
     @property
     @typing.final
     def url_name(self) -> str:
-        return self.name
+        return self
 
     @property
     def mention(self) -> str:
-        return self.name
+        return self
 
     @property
     @typing.final
     def codepoints(self) -> typing.Sequence[int]:
         """Integer codepoints that make up this emoji, as UTF-8."""
-        return [ord(c) for c in self.name]
+        return [ord(c) for c in self]
 
     @property
     def filename(self) -> str:
@@ -203,25 +190,25 @@ def url(self) -> str:
     #     This returns the name of each codepoint. If only one codepoint exists,
     #     then this will only have one item in the resulting sequence.
     #     """
-    #     return [unicodedata.name(c) for c in self.name]
+    #     return [unicodedata.name(c) for c in self]
 
     @property
     @typing.final
     def unicode_escape(self) -> str:
         """Get the unicode escape string for this emoji."""
-        return bytes(self.name, "unicode_escape").decode("utf-8")
+        return bytes(self, "unicode_escape").decode("utf-8")
 
     @classmethod
     @typing.final
     def parse_codepoints(cls, codepoint: int, *codepoints: int) -> UnicodeEmoji:
         """Create a unicode emoji from one or more UTF-32 codepoints."""
-        return cls(name="".join(map(chr, (codepoint, *codepoints))))
+        return cls("".join(map(chr, (codepoint, *codepoints))))
 
     @classmethod
     @typing.final
     def parse_unicode_escape(cls, escape: str) -> UnicodeEmoji:
         """Create a unicode emoji from a unicode escape string."""
-        return cls(name=str(escape.encode("utf-8"), "unicode_escape"))
+        return cls(escape.encode("utf-8"), "unicode_escape")
 
     @classmethod
     @typing.final
@@ -243,7 +230,7 @@ def parse(cls, string: str, /) -> UnicodeEmoji:
         # for i, codepoint in enumerate(string, start=1):
         #     unicodedata.name(codepoint)
 
-        return cls(name=string)
+        return cls(string)
 
 
 @attr_extensions.with_copy
@@ -275,21 +262,14 @@ class CustomEmoji(snowflakes.Unique, Emoji):
     id: snowflakes.Snowflake = attr.field(hash=True, repr=True)
     """The ID of this entity."""
 
-    name: typing.Optional[str] = attr.field(eq=False, hash=False, repr=True)
-    """The name of the emoji.
-
-    This can be `builtins.None` in reaction events.
-    """
+    name: str = attr.field(eq=False, hash=False, repr=True)
+    """The name of the emoji."""
 
-    is_animated: typing.Optional[bool] = attr.field(eq=False, hash=False, repr=True)
-    """Whether the emoji is animated.
-
-    Will be `builtins.None` when received in Message Reaction Remove and Message
-    Reaction Remove Emoji events.
-    """
+    is_animated: bool = attr.field(eq=False, hash=False, repr=True)
+    """Whether the emoji is animated."""
 
     def __str__(self) -> str:
-        return self.name if self.name is not None else f"Unnamed emoji ID {self.id}"
+        return self.mention
 
     @property
     def filename(self) -> str:
@@ -305,10 +285,6 @@ def url_name(self) -> str:
     def mention(self) -> str:
         return f"<{'a' if self.is_animated else ''}:{self.url_name}>"
 
-    @property
-    def is_mentionable(self) -> bool:
-        return self.is_animated is not None
-
     @property
     @typing.final
     def url(self) -> str:
@@ -318,9 +294,23 @@ def url(self) -> str:
 
     @classmethod
     def parse(cls, string: str, /) -> CustomEmoji:
-        if string.isdigit():
-            return CustomEmoji(id=snowflakes.Snowflake(string), name=None, is_animated=None)
+        """Parse a given emoji mention string into a custom emoji object.
+
+        Parameters
+        ----------
+        string : builtins.str
+            The emoji mention to parse.
 
+        Returns
+        -------
+        CustomEmoji
+            The parsed emoji object.
+
+        Raises
+        ------
+        builtins.ValueError
+            If a mention is given that has an invalid format.
+        """
         if emoji_match := _CUSTOM_EMOJI_REGEX.match(string):
             return CustomEmoji(
                 id=snowflakes.Snowflake(emoji_match.group("id")),
@@ -328,7 +318,7 @@ def parse(cls, string: str, /) -> CustomEmoji:
                 is_animated=emoji_match.group("flags").lower() == "a",
             )
 
-        raise ValueError("Expected an emoji ID or emoji mention")
+        raise ValueError("Expected an emoji mention")
 
 
 @attr.define(hash=True, kw_only=True, weakref_slot=False)
@@ -361,13 +351,6 @@ class KnownCustomEmoji(CustomEmoji):
         permission in the server the emoji is from.
     """
 
-    is_animated: bool = attr.field(eq=False, hash=False, repr=True)
-    """Whether the emoji is animated.
-
-    Unlike in `CustomEmoji`, this information is always known, and will thus
-    never be `builtins.None`.
-    """
-
     is_colons_required: bool = attr.field(eq=False, hash=False, repr=False)
     """Whether this emoji must be wrapped in colons."""
 
@@ -379,17 +362,3 @@ class KnownCustomEmoji(CustomEmoji):
 
     May be `builtins.False` due to a loss of Sever Boosts on the emoji's guild.
     """
-
-
-Emojiish = typing.Union[str, Emoji]
-r"""Type hint representing a string emoji or an `Emoji`-derived object.
-
-Examples include:
-
-- Unicode emoji strings, such as `"\N{OK HAND SIGN}"`, `"\N{OK HAND SIGN}"`,
-    `"\U0001f44c"`.
-- Custom emoji names in the format `name:id`, such as
-    `"rosaThonk:733073048646713364"`.
-- Derivative instances of `Emoji`, i.e. `UnicodeEmoji`, `CustomEmoji` and
-    `KnownCustomEmoji`.
-"""
diff --git a/hikari/events/reaction_events.py b/hikari/events/reaction_events.py
index 797139e4..faf24bcf 100644
--- a/hikari/events/reaction_events.py
+++ b/hikari/events/reaction_events.py
@@ -133,14 +133,41 @@ def user_id(self) -> snowflakes.Snowflake:
 
     @property
     @abc.abstractmethod
-    def emoji(self) -> emojis.Emoji:
-        """Emoji that was added.
+    def emoji_name(self) -> typing.Union[emojis.UnicodeEmoji, str, None]:
+        """Name of the emoji which was added if known.
+
+        !!! note
+            This will be `builtins.None` when the relevant custom emoji's data
+            is not available (e.g. the emoji has been deleted).
+
+        Returns
+        -------
+        typing.Union[hikari.emojis.UnicodeEmoji, builtins.str, builtins.None]
+            Either the string name of the custom emoji which was added
+            or the object of the `hikari.emojis.UnicodeEmoji` which was added.
+        """
+
+    @property
+    @abc.abstractmethod
+    def emoji_id(self) -> typing.Optional[snowflakes.Snowflake]:
+        """ID of the emoji which was added if it is custom.
+
+        Returns
+        -------
+        typing.Optional[hikari.snowflakes.Snowflake]
+            ID of the emoji which was added if it was a custom emoji or
+            `builtins.None`.
+        """
+
+    @property
+    @abc.abstractmethod
+    def is_animated(self) -> bool:
+        """Whether the emoji which was added is animated.
 
         Returns
         -------
-        hikari.emojis.Emoji
-            The `hikari.emojis.UnicodeEmoji` or
-            `hikari.emojis.CustomEmoji` that was added to the message.
+        builtins.bool
+            Whether the emoji which was added is animated.
         """
 
 
@@ -163,15 +190,30 @@ def user_id(self) -> snowflakes.Snowflake:
 
     @property
     @abc.abstractmethod
-    def emoji(self) -> emojis.Emoji:
-        """Emoji that was removed.
+    def emoji_name(self) -> typing.Union[emojis.UnicodeEmoji, str, None]:
+        """Name of the emoji which was removed.
+
+        !!! note
+            This will be `builtins.None` when the relevant custom emoji's data
+            is not available (e.g. the emoji has been deleted).
 
         Returns
         -------
-        hikari.emojis.Emoji
-            The `hikari.emojis.UnicodeEmoji` or
-            `hikari.emojis.CustomEmoji` that was removed from the
-            message.
+        typing.Union[hikari.emojis.UnicodeEmoji, builtins.str, builtins.None]
+            Either the string name of the custom emoji which was removed
+            or the object of the `hikari.emojis.UnicodeEmoji` which was removed.
+        """
+
+    @property
+    @abc.abstractmethod
+    def emoji_id(self) -> typing.Optional[snowflakes.Snowflake]:
+        """ID of the emoji which was removed if it was custom.
+
+        Returns
+        -------
+        typing.Optional[hikari.snowflakes.Snowflake]
+            ID of the emoji which was removed if it was a custom emoji or
+            `builtins.None`.
         """
 
 
@@ -190,15 +232,30 @@ class ReactionDeleteEmojiEvent(ReactionEvent, abc.ABC):
 
     @property
     @abc.abstractmethod
-    def emoji(self) -> emojis.Emoji:
-        """Emoji that was removed.
+    def emoji_name(self) -> typing.Union[emojis.UnicodeEmoji, str, None]:
+        """Name of the emoji which was removed if known.
+
+        !!! note
+            This will be `builtins.None` when the relevant custom emoji's data
+            is not available (e.g. the emoji has been deleted).
 
         Returns
         -------
-        hikari.emojis.Emoji
-            The `hikari.emojis.UnicodeEmoji` or
-            `hikari.emojis.CustomEmoji` that was removed from the
-            message.
+        typing.Union[hikari.emojis.UnicodeEmoji, builtins.str, builtins.None]
+            Either the string name of the custom emoji which was removed
+            or the object of the `hikari.emojis.UnicodeEmoji` which was removed.
+        """
+
+    @property
+    @abc.abstractmethod
+    def emoji_id(self) -> typing.Optional[snowflakes.Snowflake]:
+        """ID of the emoji which was removed if it was custom.
+
+        Returns
+        -------
+        typing.Optional[hikari.snowflakes.Snowflake]
+            ID of the emoji which was removed if it was a custom emoji or
+            `builtins.None`.
         """
 
 
@@ -226,7 +283,13 @@ class GuildReactionAddEvent(GuildReactionEvent, ReactionAddEvent):
     message_id: snowflakes.Snowflake = attr.field()
     # <<inherited docstring from ReactionEvent>>.
 
-    emoji: emojis.Emoji = attr.field()
+    emoji_name: typing.Union[str, emojis.UnicodeEmoji, None] = attr.field()
+    # <<inherited docstring from ReactionAddEvent>>.
+
+    emoji_id: typing.Optional[snowflakes.Snowflake] = attr.field()
+    # <<inherited docstring from ReactionAddEvent>>.
+
+    is_animated: bool = attr.field()
     # <<inherited docstring from ReactionAddEvent>>.
 
     @property
@@ -258,7 +321,7 @@ class GuildReactionDeleteEvent(GuildReactionEvent, ReactionDeleteEvent):
     # <<inherited docstring from ShardEvent>>.
 
     user_id: snowflakes.Snowflake = attr.field()
-    # <<inherited docstring from ReactionAddEvent>>.
+    # <<inherited docstring from ReactionDeleteEvent>>.
 
     guild_id: snowflakes.Snowflake = attr.field()
     # <<inherited docstring from GuildReactionEvent>>.
@@ -269,7 +332,10 @@ class GuildReactionDeleteEvent(GuildReactionEvent, ReactionDeleteEvent):
     message_id: snowflakes.Snowflake = attr.field()
     # <<inherited docstring from ReactionEvent>>.
 
-    emoji: emojis.Emoji = attr.field()
+    emoji_name: typing.Union[str, emojis.UnicodeEmoji, None] = attr.field()
+    # <<inherited docstring from ReactionDeleteEvent>>.
+
+    emoji_id: typing.Optional[snowflakes.Snowflake] = attr.field()
     # <<inherited docstring from ReactionDeleteEvent>>.
 
 
@@ -294,7 +360,10 @@ class GuildReactionDeleteEmojiEvent(GuildReactionEvent, ReactionDeleteEmojiEvent
     message_id: snowflakes.Snowflake = attr.field()
     # <<inherited docstring from ReactionEvent>>.
 
-    emoji: emojis.Emoji = attr.field()
+    emoji_name: typing.Union[str, emojis.UnicodeEmoji, None] = attr.field()
+    # <<inherited docstring from ReactionDeleteEmojiEvent>>.
+
+    emoji_id: typing.Optional[snowflakes.Snowflake] = attr.field()
     # <<inherited docstring from ReactionDeleteEmojiEvent>>.
 
 
@@ -341,7 +410,13 @@ class DMReactionAddEvent(DMReactionEvent, ReactionAddEvent):
     message_id: snowflakes.Snowflake = attr.field()
     # <<inherited docstring from ReactionEvent>>.
 
-    emoji: emojis.Emoji = attr.field()
+    emoji_name: typing.Union[str, emojis.UnicodeEmoji, None] = attr.field()
+    # <<inherited docstring from ReactionAddEvent>>.
+
+    emoji_id: typing.Optional[snowflakes.Snowflake] = attr.field()
+    # <<inherited docstring from ReactionAddEvent>>.
+
+    is_animated: bool = attr.field()
     # <<inherited docstring from ReactionAddEvent>>.
 
 
@@ -358,7 +433,7 @@ class DMReactionDeleteEvent(DMReactionEvent, ReactionDeleteEvent):
     # <<inherited docstring from ShardEvent>>.
 
     user_id: snowflakes.Snowflake = attr.field()
-    # <<inherited docstring from ReactionAddEvent>>.
+    # <<inherited docstring from ReactionDeleteEvent>>.
 
     channel_id: snowflakes.Snowflake = attr.field()
     # <<inherited docstring from ReactionEvent>>.
@@ -366,7 +441,10 @@ class DMReactionDeleteEvent(DMReactionEvent, ReactionDeleteEvent):
     message_id: snowflakes.Snowflake = attr.field()
     # <<inherited docstring from ReactionEvent>>.
 
-    emoji: emojis.Emoji = attr.field()
+    emoji_name: typing.Union[str, emojis.UnicodeEmoji, None] = attr.field()
+    # <<inherited docstring from ReactionDeleteEvent>>.
+
+    emoji_id: typing.Optional[snowflakes.Snowflake] = attr.field()
     # <<inherited docstring from ReactionDeleteEvent>>.
 
 
@@ -388,7 +466,10 @@ class DMReactionDeleteEmojiEvent(DMReactionEvent, ReactionDeleteEmojiEvent):
     message_id: snowflakes.Snowflake = attr.field()
     # <<inherited docstring from ReactionEvent>>.
 
-    emoji: emojis.Emoji = attr.field()
+    emoji_name: typing.Union[str, emojis.UnicodeEmoji, None] = attr.field()
+    # <<inherited docstring from ReactionDeleteEmojiEvent>>.
+
+    emoji_id: typing.Optional[snowflakes.Snowflake] = attr.field()
     # <<inherited docstring from ReactionDeleteEmojiEvent>>.
 
 
diff --git a/hikari/files.py b/hikari/files.py
index 993e4fd7..6a065aba 100644
--- a/hikari/files.py
+++ b/hikari/files.py
@@ -980,7 +980,7 @@ async def _wrap_iter(self) -> typing.AsyncGenerator[typing.Any, bytes]:
                 pass
 
         elif aio.is_async_iterable(self.data):
-            async for chunk in self.data:  # type: ignore[union-attr]
+            async for chunk in self.data:
                 yield self._assert_bytes(chunk)
 
         elif isinstance(self.data, typing.Iterable):
diff --git a/hikari/guilds.py b/hikari/guilds.py
index 75377d26..4e5ef439 100644
--- a/hikari/guilds.py
+++ b/hikari/guilds.py
@@ -1185,8 +1185,18 @@ class WelcomeChannel:
     description: str = attr.field(hash=False, repr=False)
     """The description shown for this channel."""
 
-    emoji: typing.Optional[emojis_.Emoji] = attr.field(default=None, kw_only=True, hash=False, repr=True)
-    """The emoji shown in the welcome screen channel if set else `builtins.None`."""
+    emoji_name: typing.Union[str, emojis_.UnicodeEmoji, None] = attr.field(
+        default=None, kw_only=True, hash=False, repr=True
+    )
+    """The emoji shown in the welcome screen channel if set to a unicode emoji.
+
+    !!! warning
+        While it may also be present for custom emojis, this is neither guaranteed
+        to be provided nor accurate.
+    """
+
+    emoji_id: typing.Optional[snowflakes.Snowflake] = attr.field(default=None, kw_only=True, hash=False, repr=True)
+    """ID of the emoji shown in the welcome screen channel if it's set to a custom emoji."""
 
 
 @attr_extensions.with_copy
diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py
index 710e5503..331e6162 100644
--- a/hikari/impl/entity_factory.py
+++ b/hikari/impl/entity_factory.py
@@ -1025,7 +1025,7 @@ def serialize_embed(  # noqa: C901 - Function too complex
     ################
 
     def deserialize_unicode_emoji(self, payload: data_binding.JSONObject) -> emoji_models.UnicodeEmoji:
-        return emoji_models.UnicodeEmoji(name=payload["name"])
+        return emoji_models.UnicodeEmoji(payload["name"])
 
     def deserialize_custom_emoji(self, payload: data_binding.JSONObject) -> emoji_models.CustomEmoji:
         return emoji_models.CustomEmoji(
@@ -1099,23 +1099,19 @@ def deserialize_welcome_screen(self, payload: data_binding.JSONObject) -> guild_
         channels: typing.List[guild_models.WelcomeChannel] = []
 
         for channel_payload in payload["welcome_channels"]:
-            emoji_id = channel_payload["emoji_id"]
-            emoji_name = channel_payload["emoji_name"]
+            raw_emoji_id = channel_payload["emoji_id"]
+            emoji_id = snowflakes.Snowflake(raw_emoji_id) if raw_emoji_id else None
 
-            emoji: typing.Optional[emoji_models.Emoji] = None
-            if emoji_name is not None:
-                if emoji_id is not None:
-                    emoji = emoji_models.CustomEmoji(
-                        id=snowflakes.Snowflake(emoji_id), name=emoji_name, is_animated=None
-                    )
-                else:
-                    emoji = emoji_models.UnicodeEmoji(emoji_name)
+            emoji_name: typing.Union[None, emoji_models.UnicodeEmoji, str]
+            if (emoji_name := channel_payload["emoji_name"]) and not emoji_id:
+                emoji_name = emoji_models.UnicodeEmoji(emoji_name)
 
             channels.append(
                 guild_models.WelcomeChannel(
                     channel_id=snowflakes.Snowflake(channel_payload["channel_id"]),
                     description=channel_payload["description"],
-                    emoji=emoji,
+                    emoji_id=emoji_id,
+                    emoji_name=emoji_name,
                 )
             )
 
@@ -1127,13 +1123,11 @@ def serialize_welcome_channel(self, welcome_channel: guild_models.WelcomeChannel
             "description": welcome_channel.description,
         }
 
-        if isinstance(welcome_channel.emoji, emoji_models.CustomEmoji):
-            payload["emoji_id"] = str(welcome_channel.emoji.id)
-            # TODO: do we need to include the name in this scenario?
-            payload["emoji_name"] = welcome_channel.emoji.name
+        if welcome_channel.emoji_id is not None:
+            payload["emoji_id"] = str(welcome_channel.emoji_id)
 
-        elif welcome_channel.emoji is not None:
-            payload["emoji_name"] = welcome_channel.emoji.name
+        elif welcome_channel.emoji_name is not None:
+            payload["emoji_name"] = str(welcome_channel.emoji_name)
 
         return payload
 
diff --git a/hikari/impl/event_factory.py b/hikari/impl/event_factory.py
index f582cea5..2d876e29 100644
--- a/hikari/impl/event_factory.py
+++ b/hikari/impl/event_factory.py
@@ -32,6 +32,7 @@
 
 from hikari import applications as application_models
 from hikari import channels as channel_models
+from hikari import emojis as emojis_models
 from hikari import snowflakes
 from hikari import undefined
 from hikari import users as user_models
@@ -53,7 +54,6 @@
 from hikari.internal import time
 
 if typing.TYPE_CHECKING:
-    from hikari import emojis as emojis_models
     from hikari import guilds as guild_models
     from hikari import invites as invite_models
     from hikari import messages as messages_models
@@ -551,7 +551,12 @@ def deserialize_message_reaction_add_event(
     ) -> reaction_events.ReactionAddEvent:
         channel_id = snowflakes.Snowflake(payload["channel_id"])
         message_id = snowflakes.Snowflake(payload["message_id"])
-        emoji = self._app.entity_factory.deserialize_emoji(payload["emoji"])
+
+        emoji_payload = payload["emoji"]
+        raw_emoji_id = emoji_payload.get("id")
+        emoji_id = snowflakes.Snowflake(raw_emoji_id) if raw_emoji_id else None
+        is_animated = bool(emoji_payload.get("animated", False))
+        emoji_name = emojis_models.UnicodeEmoji(emoji_payload["name"]) if not emoji_id else emoji_payload["name"]
 
         if "member" in payload:
             guild_id = snowflakes.Snowflake(payload["guild_id"])
@@ -561,7 +566,9 @@ def deserialize_message_reaction_add_event(
                 member=member,
                 channel_id=channel_id,
                 message_id=message_id,
-                emoji=emoji,
+                emoji_id=emoji_id,
+                emoji_name=emoji_name,
+                is_animated=is_animated,
             )
 
         user_id = snowflakes.Snowflake(payload["user_id"])
@@ -571,16 +578,26 @@ def deserialize_message_reaction_add_event(
             channel_id=channel_id,
             message_id=message_id,
             user_id=user_id,
-            emoji=emoji,
+            emoji_id=emoji_id,
+            emoji_name=emoji_name,
+            is_animated=is_animated,
         )
 
+    def _split_reaction_emoji(
+        self, emoji_payload: data_binding.JSONObject, /
+    ) -> typing.Tuple[typing.Optional[snowflakes.Snowflake], typing.Union[str, emojis_models.UnicodeEmoji, None]]:
+        if (emoji_id := emoji_payload.get("id")) is not None:
+            return snowflakes.Snowflake(emoji_id), emoji_payload["name"]
+
+        return None, emojis_models.UnicodeEmoji(emoji_payload["name"])
+
     def deserialize_message_reaction_remove_event(
         self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject
     ) -> reaction_events.ReactionDeleteEvent:
         channel_id = snowflakes.Snowflake(payload["channel_id"])
         message_id = snowflakes.Snowflake(payload["message_id"])
         user_id = snowflakes.Snowflake(payload["user_id"])
-        emoji = self._app.entity_factory.deserialize_emoji(payload["emoji"])
+        emoji_id, emoji_name = self._split_reaction_emoji(payload["emoji"])
 
         if "guild_id" in payload:
             return reaction_events.GuildReactionDeleteEvent(
@@ -590,7 +607,8 @@ def deserialize_message_reaction_remove_event(
                 guild_id=snowflakes.Snowflake(payload["guild_id"]),
                 channel_id=channel_id,
                 message_id=message_id,
-                emoji=emoji,
+                emoji_id=emoji_id,
+                emoji_name=emoji_name,
             )
 
         return reaction_events.DMReactionDeleteEvent(
@@ -599,7 +617,8 @@ def deserialize_message_reaction_remove_event(
             user_id=user_id,
             channel_id=channel_id,
             message_id=message_id,
-            emoji=emoji,
+            emoji_id=emoji_id,
+            emoji_name=emoji_name,
         )
 
     def deserialize_message_reaction_remove_all_event(
@@ -629,13 +648,14 @@ def deserialize_message_reaction_remove_emoji_event(
     ) -> reaction_events.ReactionDeleteEmojiEvent:
         channel_id = snowflakes.Snowflake(payload["channel_id"])
         message_id = snowflakes.Snowflake(payload["message_id"])
-        emoji = self._app.entity_factory.deserialize_emoji(payload["emoji"])
+        emoji_id, emoji_name = self._split_reaction_emoji(payload["emoji"])
 
         if "guild_id" in payload:
             return reaction_events.GuildReactionDeleteEmojiEvent(
                 app=self._app,
                 shard=shard,
-                emoji=emoji,
+                emoji_id=emoji_id,
+                emoji_name=emoji_name,
                 guild_id=snowflakes.Snowflake(payload["guild_id"]),
                 channel_id=channel_id,
                 message_id=message_id,
@@ -644,7 +664,8 @@ def deserialize_message_reaction_remove_emoji_event(
         return reaction_events.DMReactionDeleteEmojiEvent(
             app=self._app,
             shard=shard,
-            emoji=emoji,
+            emoji_id=emoji_id,
+            emoji_name=emoji_name,
             channel_id=channel_id,
             message_id=message_id,
         )
diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py
index 7125d6de..012d88f9 100644
--- a/hikari/impl/rest.py
+++ b/hikari/impl/rest.py
@@ -45,7 +45,6 @@
 import math
 import os
 import platform
-import re
 import sys
 import typing
 
@@ -115,10 +114,6 @@
 _X_RATELIMIT_REMAINING_HEADER: typing.Final[str] = sys.intern("X-RateLimit-Remaining")
 _X_RATELIMIT_RESET_AFTER_HEADER: typing.Final[str] = sys.intern("X-RateLimit-Reset-After")
 
-# Custom emoji mentions are in the format of <:name:id> for static emoji, or
-# <a:name:id> for animated emoji.
-_CUSTOM_EMOJI_PATTERN: typing.Final[typing.Pattern[str]] = re.compile(r"<a?:([^:]+:\d+)>")
-
 
 class ClientCredentialsStrategy(rest_api.TokenStrategy):
     """Strategy class for handling client credential OAuth2 authorization.
@@ -1527,29 +1522,31 @@ async def delete_messages(
                 raise errors.BulkDeleteError(deleted, pending) from ex
 
     @staticmethod
-    def _transform_emoji_to_url_format(emoji: emojis.Emojiish) -> str:
-        # Given an emojiish, check if it is a valid custom emoji mention. If it
-        # is, then convert it to the name:id format (remove the wrapping
-        # characters), then return it. If the emoji is an emojis.CustomEmoji
-        # directly, then get the url_name of it. All other emojis and objects
-        # can just be cast to string, as they are probably unicode emoji objects
-        # or unicode emoji strings.
-        if isinstance(emoji, emojis.CustomEmoji):
+    def _transform_emoji_to_url_format(
+        emoji: typing.Union[str, emojis.Emoji],
+        emoji_id: undefined.UndefinedOr[snowflakes.SnowflakeishOr[emojis.CustomEmoji]],
+        /,
+    ) -> str:
+        if isinstance(emoji, emojis.Emoji):
+            if emoji_id is not undefined.UNDEFINED:
+                raise ValueError("emoji_id shouldn't be passed when an Emoji object is passed for emoji")
+
             return emoji.url_name
 
-        if isinstance(emoji, str) and (custom_mention_match := _CUSTOM_EMOJI_PATTERN.match(emoji)) is not None:
-            return custom_mention_match.group(1)
+        if emoji_id is not undefined.UNDEFINED:
+            return f"{emoji}:{snowflakes.Snowflake(emoji_id)}"
 
-        return str(emoji)
+        return emoji
 
     async def add_reaction(
         self,
         channel: snowflakes.SnowflakeishOr[channels_.TextableChannel],
         message: snowflakes.SnowflakeishOr[messages_.PartialMessage],
-        emoji: emojis.Emojiish,
+        emoji: typing.Union[str, emojis.Emoji],
+        emoji_id: undefined.UndefinedOr[snowflakes.SnowflakeishOr[emojis.CustomEmoji]] = undefined.UNDEFINED,
     ) -> None:
         route = routes.PUT_MY_REACTION.compile(
-            emoji=self._transform_emoji_to_url_format(emoji),
+            emoji=self._transform_emoji_to_url_format(emoji, emoji_id),
             channel=channel,
             message=message,
         )
@@ -1559,10 +1556,11 @@ async def delete_my_reaction(
         self,
         channel: snowflakes.SnowflakeishOr[channels_.TextableChannel],
         message: snowflakes.SnowflakeishOr[messages_.PartialMessage],
-        emoji: emojis.Emojiish,
+        emoji: typing.Union[str, emojis.Emoji],
+        emoji_id: undefined.UndefinedOr[snowflakes.SnowflakeishOr[emojis.CustomEmoji]] = undefined.UNDEFINED,
     ) -> None:
         route = routes.DELETE_MY_REACTION.compile(
-            emoji=self._transform_emoji_to_url_format(emoji),
+            emoji=self._transform_emoji_to_url_format(emoji, emoji_id),
             channel=channel,
             message=message,
         )
@@ -1572,10 +1570,11 @@ async def delete_all_reactions_for_emoji(
         self,
         channel: snowflakes.SnowflakeishOr[channels_.TextableChannel],
         message: snowflakes.SnowflakeishOr[messages_.PartialMessage],
-        emoji: emojis.Emojiish,
+        emoji: typing.Union[str, emojis.Emoji],
+        emoji_id: undefined.UndefinedOr[snowflakes.SnowflakeishOr[emojis.CustomEmoji]] = undefined.UNDEFINED,
     ) -> None:
         route = routes.DELETE_REACTION_EMOJI.compile(
-            emoji=self._transform_emoji_to_url_format(emoji),
+            emoji=self._transform_emoji_to_url_format(emoji, emoji_id),
             channel=channel,
             message=message,
         )
@@ -1585,11 +1584,12 @@ async def delete_reaction(
         self,
         channel: snowflakes.SnowflakeishOr[channels_.TextableChannel],
         message: snowflakes.SnowflakeishOr[messages_.PartialMessage],
-        emoji: emojis.Emojiish,
         user: snowflakes.SnowflakeishOr[users.PartialUser],
+        emoji: typing.Union[str, emojis.Emoji],
+        emoji_id: undefined.UndefinedOr[snowflakes.SnowflakeishOr[emojis.CustomEmoji]] = undefined.UNDEFINED,
     ) -> None:
         route = routes.DELETE_REACTION_USER.compile(
-            emoji=self._transform_emoji_to_url_format(emoji),
+            emoji=self._transform_emoji_to_url_format(emoji, emoji_id),
             channel=channel,
             message=message,
             user=user,
@@ -1608,14 +1608,15 @@ def fetch_reactions_for_emoji(
         self,
         channel: snowflakes.SnowflakeishOr[channels_.TextableChannel],
         message: snowflakes.SnowflakeishOr[messages_.PartialMessage],
-        emoji: emojis.Emojiish,
+        emoji: typing.Union[str, emojis.Emoji],
+        emoji_id: undefined.UndefinedOr[snowflakes.SnowflakeishOr[emojis.CustomEmoji]] = undefined.UNDEFINED,
     ) -> iterators.LazyIterator[users.User]:
         return special_endpoints_impl.ReactorIterator(
             entity_factory=self._entity_factory,
             request_call=self._request,
             channel=channel,
             message=message,
-            emoji=self._transform_emoji_to_url_format(emoji),
+            emoji=self._transform_emoji_to_url_format(emoji, emoji_id),
         )
 
     async def create_webhook(
diff --git a/hikari/internal/aio.py b/hikari/internal/aio.py
index 977844cb..20bbdd63 100644
--- a/hikari/internal/aio.py
+++ b/hikari/internal/aio.py
@@ -37,6 +37,10 @@
 import inspect
 import typing
 
+if typing.TYPE_CHECKING:
+    # typing_extensions is a dependency of mypy, and pyright vendors it.
+    from typing_extensions import TypeGuard
+
 T_co = typing.TypeVar("T_co", covariant=True)
 T_inv = typing.TypeVar("T_inv")
 
@@ -84,12 +88,12 @@ def completed_future(result: typing.Optional[T_inv] = None, /) -> asyncio.Future
 # ... so I guess I will have to determine this some other way.
 
 
-def is_async_iterator(obj: typing.Any) -> bool:
+def is_async_iterator(obj: typing.Any) -> TypeGuard[typing.AsyncIterator[object]]:
     """Determine if the object is an async iterator or not."""
     return asyncio.iscoroutinefunction(getattr(obj, "__anext__", None))
 
 
-def is_async_iterable(obj: typing.Any) -> bool:
+def is_async_iterable(obj: typing.Any) -> TypeGuard[typing.AsyncIterable[object]]:
     """Determine if the object is an async iterable or not."""
     attr = getattr(obj, "__aiter__", None)
     return inspect.isfunction(attr) or inspect.ismethod(attr)
diff --git a/hikari/internal/cache.py b/hikari/internal/cache.py
index da981568..fa23ac04 100644
--- a/hikari/internal/cache.py
+++ b/hikari/internal/cache.py
@@ -83,80 +83,77 @@
 """Type-hint for mapping values."""
 
 
-class CacheMappingView(cache.CacheView[KeyT, ValueT], typing.Generic[KeyT, ValueT]):
+class CacheMappingView(cache.CacheView[KeyT, ValueT]):
     """A cache mapping view implementation used for representing cached data.
 
     Parameters
     ----------
-    items : typing.Mapping[KeyT, typing.Union[ValueT, DataT, RefCell[ValueT]]]
+    items : typing.Union[typing.Mapping[KeyT, ValueT], typing.Mapping[KeyT, DataT]]
         A mapping of keys to the values in their raw forms, wrapped by a ref
         wrapper or in a data form.
     builder : typing.Optional[typing.Callable[[DataT], ValueT]]
         The callable used to build entities before they're returned by the
         mapping. This is used to cover the case when items stores `DataT` objects.
-    predicate : typing.Optional[typing.Callable[[typing.Any], bool]]
-        A callable to use to determine whether entries should be returned or hidden,
-        this should take in whatever raw type was passed for the value in `items`.
-        This may be `builtins.None` if all entries should be exposed.
     """
 
-    __slots__: typing.Sequence[str] = ("_builder", "_data", "_predicate")
+    __slots__: typing.Sequence[str] = ("_data", "_builder")
 
+    @typing.overload
     def __init__(
         self,
-        items: typing.Mapping[KeyT, typing.Union[ValueT, DataT]],
+        items: typing.Mapping[KeyT, ValueT],
+    ) -> None:
+        ...
+
+    @typing.overload
+    def __init__(
+        self,
+        items: typing.Mapping[KeyT, DataT],
+        *,
+        builder: typing.Callable[[DataT], ValueT],
+    ) -> None:
+        ...
+
+    def __init__(
+        self,
+        items: typing.Union[typing.Mapping[KeyT, ValueT], typing.Mapping[KeyT, DataT]],
         *,
         builder: typing.Optional[typing.Callable[[DataT], ValueT]] = None,
-        predicate: typing.Optional[typing.Callable[[typing.Any], bool]] = None,
     ) -> None:
         self._builder = builder
         self._data = items
-        self._predicate = predicate
 
-    @classmethod
-    def _copy(cls, value: ValueT) -> ValueT:
+    @staticmethod
+    def _copy(value: ValueT) -> ValueT:
         return copy.copy(value)
 
     def __contains__(self, key: typing.Any) -> bool:
-        return key in self._data and (self._predicate is None or self._predicate(self._data[key]))
+        return key in self._data
 
     def __getitem__(self, key: KeyT) -> ValueT:
         entry = self._data[key]
 
-        if self._predicate is not None and not self._predicate(entry):
-            raise KeyError(key)
-
-        if self._builder is not None:
-            entry = self._builder(entry)  # type: ignore[arg-type]
-
-        else:
-            entry = self._copy(entry)  # type: ignore[arg-type]
+        if self._builder:
+            return self._builder(entry)  # type: ignore[arg-type]
 
-        return entry
+        return self._copy(entry)  # type: ignore[arg-type]
 
     def __iter__(self) -> typing.Iterator[KeyT]:
-        if self._predicate is None:
-            return iter(self._data)
-        else:
-            return (key for key, value in self._data.items() if self._predicate(value))
+        return iter(self._data)
 
     def __len__(self) -> int:
-        if self._predicate is None:
-            return len(self._data)
-        else:
-            return sum(1 for value in self._data.values() if self._predicate(value))
+        return len(self._data)
 
-    def get_item_at(self, index: int) -> ValueT:
-        current_index = -1
+    @typing.overload
+    def get_item_at(self, index: int, /) -> ValueT:
+        ...
 
-        for key, value in self._data.items():
-            if self._predicate is None or self._predicate(value):
-                index += 1
+    @typing.overload
+    def get_item_at(self, index: slice, /) -> typing.Sequence[ValueT]:
+        ...
 
-            if current_index == index:
-                return self[key]
-
-        raise IndexError(index)
+    def get_item_at(self, index: typing.Union[slice, int], /) -> typing.Union[ValueT, typing.Sequence[ValueT]]:
+        return collections.get_index_or_slice(self, index)
 
     def iterator(self) -> iterators.LazyIterator[ValueT]:
         return iterators.FlatLazyIterator(self.values())
@@ -179,7 +176,7 @@ def __iter__(self) -> typing.Iterator[typing.Any]:
     def __len__(self) -> typing.Literal[0]:
         return 0
 
-    def get_item_at(self, index: int) -> typing.NoReturn:
+    def get_item_at(self, index: typing.Union[slice, int]) -> typing.NoReturn:
         raise IndexError(index)
 
     def iterator(self) -> iterators.LazyIterator[ValueT]:
@@ -453,7 +450,7 @@ class KnownCustomEmojiData(BaseData[emojis.KnownCustomEmoji]):
     """A data model for storing known custom emoji data in an in-memory cache."""
 
     id: snowflakes.Snowflake = attr.field()
-    name: typing.Optional[str] = attr.field()
+    name: str = attr.field()
     is_animated: bool = attr.field()
     guild_id: snowflakes.Snowflake = attr.field()
     role_ids: typing.Tuple[snowflakes.Snowflake, ...] = attr.field()
@@ -740,7 +737,7 @@ def _copy_embed(embed: embeds_.Embed) -> embeds_.Embed:
         author=copy.copy(embed.author) if embed.author else None,
         provider=copy.copy(embed.provider) if embed.provider else None,
         footer=copy.copy(embed.footer) if embed.footer else None,
-        fields=list(map(copy.copy, embed.fields)),  # type: ignore[arg-type]
+        fields=[copy.copy(field) for field in embed.fields],
     )
 
 
@@ -1028,6 +1025,6 @@ class Cache3DMappingView(CacheMappingView[snowflakes.Snowflake, cache.CacheView[
 
     __slots__: typing.Sequence[str] = ()
 
-    @classmethod
-    def _copy(cls, value: cache.CacheView[KeyT, ValueT]) -> cache.CacheView[KeyT, ValueT]:
+    @staticmethod
+    def _copy(value: cache.CacheView[KeyT, ValueT]) -> cache.CacheView[KeyT, ValueT]:
         return value
diff --git a/hikari/messages.py b/hikari/messages.py
index fbc51fdc..be1ba624 100644
--- a/hikari/messages.py
+++ b/hikari/messages.py
@@ -1080,19 +1080,46 @@ async def delete(self) -> None:
         """
         await self.app.rest.delete_message(self.channel_id, self.id)
 
-    async def add_reaction(self, emoji: emojis_.Emojiish) -> None:
+    @typing.overload
+    async def add_reaction(
+        self,
+        emoji: typing.Union[str, emojis_.Emoji],
+    ) -> None:
+        ...
+
+    @typing.overload
+    async def add_reaction(
+        self,
+        emoji: str,
+        emoji_id: snowflakes.SnowflakeishOr[emojis_.CustomEmoji],
+    ) -> None:
+        ...
+
+    async def add_reaction(
+        self,
+        emoji: typing.Union[str, emojis_.Emoji],
+        emoji_id: undefined.UndefinedOr[snowflakes.SnowflakeishOr[emojis_.CustomEmoji]] = undefined.UNDEFINED,
+    ) -> None:
         r"""Add a reaction to this message.
 
         Parameters
         ----------
-        emoji : hikari.emojis.Emojiish
-            The emoji to add. This may be a unicode emoji string, the
-            `name:id` of a custom emoji, or a subclass of
-            `hikari.emojis.Emoji`.
+        emoji: typing.Union[builtins.str, hikari.emojis.Emoji]
+            Object or name of the emoji to react with.
 
             Note that if the emoji is an `hikari.emojis.CustomEmoji`
             and is not from a guild the bot user is in, then this will fail.
 
+        Other Parameters
+        ----------------
+        emoji_id : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.emojis.CustomEmoji]]
+            ID of the custom emoji to react with.
+            This should only be provided when a custom emoji's name is passed
+            for `emoji`.
+
+            Note that this will fail if the emoji is from a guild the bot isn't
+            in.
+
         Examples
         --------
         ```py
@@ -1102,12 +1129,8 @@ async def add_reaction(self, emoji: emojis_.Emojiish) -> None:
         # Using a unicode emoji name.
         await message.add_reaction("\N{OK HAND SIGN}")
 
-        # Using the `name:id` format.
-        await message.add_reaction("rooAYAYA:705837374319493284")
-
-        # Using a raw custom emoji mention (unanimated and animated)
-        await message.add_reaction("<:rooAYAYA:705837374319493284>")
-        await message.add_reaction("<a:rooAYAYA:705837374319493284>")
+        # Using the name and id.
+        await message.add_reaction("rooAYAYA", 705837374319493284)
 
         # Using an Emoji-derived object.
         await message.add_reaction(some_emoji_object)
@@ -1129,11 +1152,31 @@ async def add_reaction(self, emoji: emojis_.Emojiish) -> None:
             guild you are not part of if no one else has previously
             reacted with the same emoji.
         """
-        await self.app.rest.add_reaction(channel=self.channel_id, message=self.id, emoji=emoji)
+        await self.app.rest.add_reaction(channel=self.channel_id, message=self.id, emoji=emoji, emoji_id=emoji_id)
+
+    @typing.overload
+    async def remove_reaction(
+        self,
+        emoji: typing.Union[str, emojis_.Emoji],
+        *,
+        user: undefined.UndefinedOr[snowflakes.SnowflakeishOr[users_.PartialUser]] = undefined.UNDEFINED,
+    ) -> None:
+        ...
 
+    @typing.overload
     async def remove_reaction(
         self,
-        emoji: emojis_.Emojiish,
+        emoji: str,
+        emoji_id: snowflakes.SnowflakeishOr[emojis_.CustomEmoji],
+        *,
+        user: undefined.UndefinedOr[snowflakes.SnowflakeishOr[users_.PartialUser]] = undefined.UNDEFINED,
+    ) -> None:
+        ...
+
+    async def remove_reaction(
+        self,
+        emoji: typing.Union[str, emojis_.Emoji],
+        emoji_id: undefined.UndefinedOr[snowflakes.SnowflakeishOr[emojis_.CustomEmoji]] = undefined.UNDEFINED,
         *,
         user: undefined.UndefinedOr[snowflakes.SnowflakeishOr[users_.PartialUser]] = undefined.UNDEFINED,
     ) -> None:
@@ -1141,11 +1184,15 @@ async def remove_reaction(
 
         Parameters
         ----------
-        emoji : hikari.emojis.Emojiish
-            The emoji to remove.
+        emoji : typing.Union[builtins.str, hikari.emojis.Emoji]
+            Object or name of the emoji to remove the reaction for.
 
         Other Parameters
         ----------------
+        emoji_id : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.emojis.CustomEmoji]]
+            ID of the custom emoji to remove the reaction for.
+            This should only be provided when a custom emoji's name is passed
+            for `emoji`.
         user : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.users.PartialUser]]
             The user of the reaction to remove. If unspecified, then the bot's
             reaction is removed instead.
@@ -1160,9 +1207,8 @@ async def remove_reaction(
             # reaction.
             await message.remove_reaction("\N{OK HAND SIGN}", some_user)
 
-            # Using a raw custom emoji mention (unanimated and animated)
-            await message.remove_reaction("<:rooAYAYA:705837374319493284>", some_user)
-            await message.remove_reaction("<a:rooAYAYA:705837374319493284>", some_user)
+            # Using the name and id.
+            await message.add_reaction("rooAYAYA", 705837374319493284)
 
             # Using an Emoji object and removing a specific user from this
             # reaction.
@@ -1185,18 +1231,49 @@ async def remove_reaction(
             found.
         """
         if user is undefined.UNDEFINED:
-            await self.app.rest.delete_my_reaction(channel=self.channel_id, message=self.id, emoji=emoji)
+            await self.app.rest.delete_my_reaction(
+                channel=self.channel_id, message=self.id, emoji=emoji, emoji_id=emoji_id
+            )
         else:
-            await self.app.rest.delete_reaction(channel=self.channel_id, message=self.id, emoji=emoji, user=user)
+            await self.app.rest.delete_reaction(
+                channel=self.channel_id, message=self.id, emoji=emoji, emoji_id=emoji_id, user=user
+            )
+
+    @typing.overload
+    async def remove_all_reactions(self) -> None:
+        ...
 
-    async def remove_all_reactions(self, emoji: undefined.UndefinedOr[emojis_.Emojiish] = undefined.UNDEFINED) -> None:
+    @typing.overload
+    async def remove_all_reactions(
+        self,
+        emoji: typing.Union[str, emojis_.Emoji],
+    ) -> None:
+        ...
+
+    @typing.overload
+    async def remove_all_reactions(
+        self,
+        emoji: str,
+        emoji_id: snowflakes.SnowflakeishOr[emojis_.CustomEmoji],
+    ) -> None:
+        ...
+
+    async def remove_all_reactions(
+        self,
+        emoji: undefined.UndefinedOr[typing.Union[str, emojis_.Emoji]] = undefined.UNDEFINED,
+        emoji_id: undefined.UndefinedOr[snowflakes.SnowflakeishOr[emojis_.CustomEmoji]] = undefined.UNDEFINED,
+    ) -> None:
         r"""Remove all users' reactions for a specific emoji from the message.
 
         Other Parameters
         ----------------
-        emoji : hikari.undefined.UndefinedOr[hikari.emojis.Emojiish]
-            The emoji to remove all reactions for. If not specified, then all
-            emojis are removed.
+        emoji : hikari.undefined.UndefinedOr[typing.Union[builtins.str, hikari.emojis.Emoji]]
+            Object or name of the emoji to get the reactions for. If not specified
+            then all reactions are removed.
+        emoji_id : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.emojis.CustomEmoji]]
+            ID of the custom emoji to react with.
+            This should only be provided when a custom emoji's name is passed
+            for `emoji`.
 
         Example
         --------
@@ -1204,9 +1281,8 @@ async def remove_all_reactions(self, emoji: undefined.UndefinedOr[emojis_.Emojii
             # reaction.
             await message.remove_all_reactions("\N{OK HAND SIGN}")
 
-            # Using a raw custom emoji mention (unanimated and animated)
-            await message.remove_all_reactions("<:rooAYAYA:705837374319493284>")
-            await message.remove_all_reactions("<a:rooAYAYA:705837374319493284>")
+            # Using the name and id.
+            await message.add_reaction("rooAYAYA", 705837374319493284)
 
             # Removing all reactions entirely.
             await message.remove_all_reactions()
@@ -1227,7 +1303,9 @@ async def remove_all_reactions(self, emoji: undefined.UndefinedOr[emojis_.Emojii
         if emoji is undefined.UNDEFINED:
             await self.app.rest.delete_all_reactions(channel=self.channel_id, message=self.id)
         else:
-            await self.app.rest.delete_all_reactions_for_emoji(channel=self.channel_id, message=self.id, emoji=emoji)
+            await self.app.rest.delete_all_reactions_for_emoji(
+                channel=self.channel_id, message=self.id, emoji=emoji, emoji_id=emoji_id
+            )
 
 
 @attr.define(hash=True, kw_only=True, weakref_slot=False, auto_attribs=False)


Test patch:
diff --git a/tests/hikari/events/test_reaction_events.py b/tests/hikari/events/test_reaction_events.py
index fc8394c0..121b198a 100644
--- a/tests/hikari/events/test_reaction_events.py
+++ b/tests/hikari/events/test_reaction_events.py
@@ -31,7 +31,13 @@ class TestGuildReactionAddEvent:
     @pytest.fixture()
     def event(self):
         return reaction_events.GuildReactionAddEvent(
-            shard=object(), member=mock.MagicMock(guilds.Member), channel_id=123, message_id=456, emoji="👌"
+            shard=object(),
+            member=mock.MagicMock(guilds.Member),
+            channel_id=123,
+            message_id=456,
+            emoji_name="👌",
+            emoji_id=None,
+            is_animated=False,
         )
 
     def test_app_property(self, event):
diff --git a/tests/hikari/impl/test_entity_factory.py b/tests/hikari/impl/test_entity_factory.py
index 2da36cbb..bd584fc5 100644
--- a/tests/hikari/impl/test_entity_factory.py
+++ b/tests/hikari/impl/test_entity_factory.py
@@ -1811,6 +1811,7 @@ def guild_welcome_screen_payload(self):
                     "emoji_id": None,
                     "emoji_name": None,
                 },
+                {"channel_id": "92929292929", "description": "hi there", "emoji_id": "49494949", "emoji_name": None},
             ],
         }
 
@@ -1821,33 +1822,47 @@ def test_deserialize_welcome_screen(self, entity_factory_impl, mock_app, guild_w
 
         assert welcome_screen.channels[0].channel_id == 87656344532234
         assert welcome_screen.channels[0].description == "Follow for nothing"
-        assert isinstance(welcome_screen.channels[0].emoji, emoji_models.UnicodeEmoji)
-        assert welcome_screen.channels[0].emoji.name == "📡"
-        assert isinstance(welcome_screen.channels[1].emoji, emoji_models.CustomEmoji)
-        assert welcome_screen.channels[1].emoji.name == "dogGoesMeow"
-        assert welcome_screen.channels[1].emoji.id == 31231351234
-        assert welcome_screen.channels[1].emoji.is_animated is None
+
+        assert isinstance(welcome_screen.channels[0].emoji_name, emoji_models.UnicodeEmoji)
+        assert welcome_screen.channels[0].emoji_name == "📡"
+        assert welcome_screen.channels[0].emoji_id is None
+
+        assert not isinstance(welcome_screen.channels[1].emoji_name, emoji_models.UnicodeEmoji)
+        assert welcome_screen.channels[1].emoji_name == "dogGoesMeow"
+        assert welcome_screen.channels[1].emoji_id == 31231351234
+
+        assert welcome_screen.channels[2].emoji_name is None
+        assert welcome_screen.channels[2].emoji_id is None
+
+        assert welcome_screen.channels[3].emoji_name is None
+        assert welcome_screen.channels[3].emoji_id == 49494949
 
     def test_serialize_welcome_channel_with_custom_emoji(self, entity_factory_impl, mock_app):
         channel = guild_models.WelcomeChannel(
             channel_id=snowflakes.Snowflake(431231),
             description="meow",
-            emoji=emoji_models.CustomEmoji(id=snowflakes.Snowflake(564123), name="boom", is_animated=None),
+            emoji_id=snowflakes.Snowflake(564123),
+            emoji_name="boom",
         )
         result = entity_factory_impl.serialize_welcome_channel(channel)
 
-        assert result == {"channel_id": "431231", "description": "meow", "emoji_id": "564123", "emoji_name": "boom"}
+        assert result == {"channel_id": "431231", "description": "meow", "emoji_id": "564123"}
 
     def test_serialize_welcome_channel_with_unicode_emoji(self, entity_factory_impl, mock_app):
         channel = guild_models.WelcomeChannel(
-            channel_id=snowflakes.Snowflake(4312311), description="meow1", emoji=emoji_models.UnicodeEmoji("a")
+            channel_id=snowflakes.Snowflake(4312311),
+            description="meow1",
+            emoji_name=emoji_models.UnicodeEmoji("a"),
+            emoji_id=None,
         )
         result = entity_factory_impl.serialize_welcome_channel(channel)
 
         assert result == {"channel_id": "4312311", "description": "meow1", "emoji_name": "a"}
 
     def test_serialize_welcome_channel_with_no_emoji(self, entity_factory_impl, mock_app):
-        channel = guild_models.WelcomeChannel(channel_id=snowflakes.Snowflake(4312312), description="meow2", emoji=None)
+        channel = guild_models.WelcomeChannel(
+            channel_id=snowflakes.Snowflake(4312312), description="meow2", emoji_id=None, emoji_name=None
+        )
         result = entity_factory_impl.serialize_welcome_channel(channel)
 
         assert result == {"channel_id": "4312312", "description": "meow2"}
diff --git a/tests/hikari/impl/test_event_factory.py b/tests/hikari/impl/test_event_factory.py
index 61293762..6389c39a 100644
--- a/tests/hikari/impl/test_event_factory.py
+++ b/tests/hikari/impl/test_event_factory.py
@@ -25,6 +25,7 @@
 import pytest
 
 from hikari import channels as channel_models
+from hikari import emojis as emoji_models
 from hikari import traits
 from hikari import undefined
 from hikari import users as user_models
@@ -705,59 +706,155 @@ def test_deserialize_message_delete_bulk_event_in_dm(self, event_factory, mock_a
 
     def test_deserialize_message_reaction_add_event_in_guild(self, event_factory, mock_shard, mock_app):
         mock_member_payload = mock.Mock(app=mock_app)
-        mock_emoji_payload = object()
         mock_payload = {
             "member": mock_member_payload,
             "channel_id": "34123",
             "message_id": "43123123",
             "guild_id": "43949494",
-            "emoji": mock_emoji_payload,
+            "emoji": {"id": "123312", "name": "okok", "animated": True},
         }
 
         event = event_factory.deserialize_message_reaction_add_event(mock_shard, mock_payload)
 
-        mock_app.entity_factory.deserialize_emoji.assert_called_once_with(mock_emoji_payload)
         mock_app.entity_factory.deserialize_member.assert_called_once_with(mock_member_payload, guild_id=43949494)
         assert isinstance(event, reaction_events.GuildReactionAddEvent)
         assert event.shard is mock_shard
         assert event.channel_id == 34123
         assert event.message_id == 43123123
         assert event.member is mock_app.entity_factory.deserialize_member.return_value
-        assert event.emoji is mock_app.entity_factory.deserialize_emoji.return_value
+        assert not isinstance(event.emoji_name, emoji_models.UnicodeEmoji)
+        assert event.emoji_name == "okok"
+        assert event.emoji_id == 123312
+        assert event.is_animated is True
+
+    def test_deserialize_message_reaction_add_event_in_guild_when_partial_custom(
+        self, event_factory, mock_shard, mock_app
+    ):
+        mock_member_payload = object()
+        mock_payload = {
+            "member": mock_member_payload,
+            "channel_id": "34123",
+            "message_id": "43123123",
+            "guild_id": "43949494",
+            "emoji": {"id": "123312", "name": None},
+        }
+
+        event = event_factory.deserialize_message_reaction_add_event(mock_shard, mock_payload)
+
+        assert event.is_animated is False
+        assert event.emoji_id == 123312
+        assert event.emoji_name is None
+
+    def test_deserialize_message_reaction_add_event_in_guild_when_unicode(self, event_factory, mock_shard, mock_app):
+        mock_member_payload = object()
+        mock_payload = {
+            "member": mock_member_payload,
+            "channel_id": "34123",
+            "message_id": "43123123",
+            "guild_id": "43949494",
+            "emoji": {"name": "hi", "id": None},
+        }
+
+        event = event_factory.deserialize_message_reaction_add_event(mock_shard, mock_payload)
+
+        assert isinstance(event.emoji_name, emoji_models.UnicodeEmoji)
+        assert event.emoji_name == "hi"
+        assert event.emoji_id is None
+        assert event.is_animated is False
 
     def test_deserialize_message_reaction_add_event_in_dm(self, event_factory, mock_shard, mock_app):
-        mock_emoji_payload = object()
         mock_payload = {
             "channel_id": "34123",
             "message_id": "43123123",
             "user_id": "43949494",
-            "emoji": mock_emoji_payload,
+            "emoji": {"id": "3293939", "name": "vohio", "animated": True},
+        }
+
+        event = event_factory.deserialize_message_reaction_add_event(mock_shard, mock_payload)
+
+        assert isinstance(event, reaction_events.DMReactionAddEvent)
+        assert event.app is mock_app
+        assert event.shard is mock_shard
+        assert event.channel_id == 34123
+        assert event.message_id == 43123123
+        assert event.user_id == 43949494
+        assert not isinstance(event.emoji_name, emoji_models.UnicodeEmoji)
+        assert event.emoji_name == "vohio"
+        assert event.emoji_id == 3293939
+        assert event.is_animated is True
+
+    def test_deserialize_message_reaction_add_event_in_dm_when_partial_custom(
+        self, event_factory, mock_shard, mock_app
+    ):
+        mock_payload = {
+            "channel_id": "34123",
+            "message_id": "43123123",
+            "user_id": "43949494",
+            "emoji": {"id": "3293939", "name": None},
+        }
+
+        event = event_factory.deserialize_message_reaction_add_event(mock_shard, mock_payload)
+
+        assert event.emoji_name is None
+        assert event.emoji_id == 3293939
+        assert event.is_animated is False
+
+    def test_deserialize_message_reaction_add_event_in_dm_when_unicode(self, event_factory, mock_shard, mock_app):
+        mock_payload = {
+            "channel_id": "34123",
+            "message_id": "43123123",
+            "user_id": "43949494",
+            "emoji": {"name": "bye"},
         }
 
         event = event_factory.deserialize_message_reaction_add_event(mock_shard, mock_payload)
 
-        mock_app.entity_factory.deserialize_emoji.assert_called_once_with(mock_emoji_payload)
         assert isinstance(event, reaction_events.DMReactionAddEvent)
         assert event.app is mock_app
         assert event.shard is mock_shard
         assert event.channel_id == 34123
         assert event.message_id == 43123123
         assert event.user_id == 43949494
-        assert event.emoji is mock_app.entity_factory.deserialize_emoji.return_value
+        assert isinstance(event.emoji_name, emoji_models.UnicodeEmoji)
+        assert event.emoji_name == "bye"
+        assert event.emoji_id is None
+        assert event.is_animated is False
 
     def test_deserialize_message_reaction_remove_event_in_guild(self, event_factory, mock_app, mock_shard):
-        mock_emoji_payload = object()
         mock_payload = {
             "user_id": "43123",
             "channel_id": "484848",
             "message_id": "43234",
             "guild_id": "383838",
-            "emoji": mock_emoji_payload,
+            "emoji": {"id": "123432", "name": "fififiif"},
+        }
+
+        event = event_factory.deserialize_message_reaction_remove_event(mock_shard, mock_payload)
+
+        assert isinstance(event, reaction_events.GuildReactionDeleteEvent)
+        assert event.app is mock_app
+        assert event.shard is mock_shard
+        assert event.user_id == 43123
+        assert event.channel_id == 484848
+        assert event.message_id == 43234
+        assert event.guild_id == 383838
+        assert event.emoji_id == 123432
+        assert event.emoji_name == "fififiif"
+        assert not isinstance(event.emoji_name, emoji_models.UnicodeEmoji)
+
+    def test_deserialize_message_reaction_remove_event_in_guild_with_unicode_emoji(
+        self, event_factory, mock_app, mock_shard
+    ):
+        mock_payload = {
+            "user_id": "43123",
+            "channel_id": "484848",
+            "message_id": "43234",
+            "guild_id": "383838",
+            "emoji": {"name": "o"},
         }
 
         event = event_factory.deserialize_message_reaction_remove_event(mock_shard, mock_payload)
 
-        mock_app.entity_factory.deserialize_emoji.assert_called_once_with(mock_emoji_payload)
         assert isinstance(event, reaction_events.GuildReactionDeleteEvent)
         assert event.app is mock_app
         assert event.shard is mock_shard
@@ -765,22 +862,46 @@ def test_deserialize_message_reaction_remove_event_in_guild(self, event_factory,
         assert event.channel_id == 484848
         assert event.message_id == 43234
         assert event.guild_id == 383838
-        assert event.emoji is mock_app.entity_factory.deserialize_emoji.return_value
+        assert event.emoji_id is None
+        assert event.emoji_name == "o"
+        assert isinstance(event.emoji_name, emoji_models.UnicodeEmoji)
 
     def test_deserialize_message_reaction_remove_event_in_dm(self, event_factory, mock_app, mock_shard):
-        mock_emoji_payload = object()
-        mock_payload = {"user_id": "43123", "channel_id": "484848", "message_id": "43234", "emoji": mock_emoji_payload}
+        mock_payload = {
+            "user_id": "43123",
+            "channel_id": "484848",
+            "message_id": "43234",
+            "emoji": {"id": "123123", "name": "okok"},
+        }
+
+        event = event_factory.deserialize_message_reaction_remove_event(mock_shard, mock_payload)
+
+        assert isinstance(event, reaction_events.DMReactionDeleteEvent)
+        assert event.app is mock_app
+        assert event.shard is mock_shard
+        assert event.user_id == 43123
+        assert event.channel_id == 484848
+        assert event.message_id == 43234
+        assert not isinstance(event.emoji_name, emoji_models.UnicodeEmoji)
+        assert event.emoji_name == "okok"
+        assert event.emoji_id == 123123
+
+    def test_deserialize_message_reaction_remove_event_in_dm_with_unicode_emoji(
+        self, event_factory, mock_app, mock_shard
+    ):
+        mock_payload = {"user_id": "43123", "channel_id": "484848", "message_id": "43234", "emoji": {"name": "wwww"}}
 
         event = event_factory.deserialize_message_reaction_remove_event(mock_shard, mock_payload)
 
-        mock_app.entity_factory.deserialize_emoji.assert_called_once_with(mock_emoji_payload)
         assert isinstance(event, reaction_events.DMReactionDeleteEvent)
         assert event.app is mock_app
         assert event.shard is mock_shard
         assert event.user_id == 43123
         assert event.channel_id == 484848
         assert event.message_id == 43234
-        assert event.emoji is mock_app.entity_factory.deserialize_emoji.return_value
+        assert isinstance(event.emoji_name, emoji_models.UnicodeEmoji)
+        assert event.emoji_name == "wwww"
+        assert event.emoji_id is None
 
     def test_deserialize_message_reaction_remove_all_event_in_guild(self, event_factory, mock_app, mock_shard):
         mock_payload = {"channel_id": "312312", "message_id": "34323", "guild_id": "393939"}
@@ -806,38 +927,69 @@ def test_deserialize_message_reaction_remove_all_event_in_dm(self, event_factory
         assert event.message_id == 34323
 
     def test_deserialize_message_reaction_remove_emoji_event_in_guild(self, event_factory, mock_app, mock_shard):
-        mock_emoji_payload = object()
         mock_payload = {
             "channel_id": "123123",
             "guild_id": "423412",
             "message_id": "99999",
-            "emoji": mock_emoji_payload,
+            "emoji": {"id": "3123123", "name": "okokok"},
         }
 
         event = event_factory.deserialize_message_reaction_remove_emoji_event(mock_shard, mock_payload)
 
-        mock_app.entity_factory.deserialize_emoji.assert_called_once_with(mock_emoji_payload)
         assert isinstance(event, reaction_events.GuildReactionDeleteEmojiEvent)
         assert event.app is mock_app
         assert event.shard is mock_shard
         assert event.channel_id == 123123
         assert event.guild_id == 423412
         assert event.message_id == 99999
-        assert event.emoji is mock_app.entity_factory.deserialize_emoji.return_value
+        assert event.emoji_id == 3123123
+        assert event.emoji_name == "okokok"
+        assert not isinstance(event.emoji_name, emoji_models.UnicodeEmoji)
+
+    def test_deserialize_message_reaction_remove_emoji_event_in_guild_with_unicode_emoji(
+        self, event_factory, mock_app, mock_shard
+    ):
+        mock_payload = {
+            "channel_id": "123123",
+            "guild_id": "423412",
+            "message_id": "99999",
+            "emoji": {"name": "okokok"},
+        }
+
+        event = event_factory.deserialize_message_reaction_remove_emoji_event(mock_shard, mock_payload)
+
+        assert event.emoji_name == "okokok"
+        assert isinstance(event.emoji_name, emoji_models.UnicodeEmoji)
 
     def test_deserialize_message_reaction_remove_emoji_event_in_dm(self, event_factory, mock_app, mock_shard):
-        mock_emoji_payload = object()
-        mock_payload = {"channel_id": "123123", "message_id": "99999", "emoji": mock_emoji_payload}
+        mock_payload = {"channel_id": "123123", "message_id": "99999", "emoji": {"id": "123321", "name": "nom"}}
+
+        event = event_factory.deserialize_message_reaction_remove_emoji_event(mock_shard, mock_payload)
+
+        assert isinstance(event, reaction_events.DMReactionDeleteEmojiEvent)
+        assert event.app is mock_app
+        assert event.shard is mock_shard
+        assert event.channel_id == 123123
+        assert event.message_id == 99999
+        assert event.emoji_id == 123321
+        assert event.emoji_name == "nom"
+        assert not isinstance(event.emoji_name, emoji_models.UnicodeEmoji)
+
+    def test_deserialize_message_reaction_remove_emoji_event_in_dm_with_unicode_emoji(
+        self, event_factory, mock_app, mock_shard
+    ):
+        mock_payload = {"channel_id": "123123", "message_id": "99999", "emoji": {"name": "gg"}}
 
         event = event_factory.deserialize_message_reaction_remove_emoji_event(mock_shard, mock_payload)
 
-        mock_app.entity_factory.deserialize_emoji.assert_called_once_with(mock_emoji_payload)
         assert isinstance(event, reaction_events.DMReactionDeleteEmojiEvent)
         assert event.app is mock_app
         assert event.shard is mock_shard
         assert event.channel_id == 123123
         assert event.message_id == 99999
-        assert event.emoji is mock_app.entity_factory.deserialize_emoji.return_value
+        assert event.emoji_id is None
+        assert event.emoji_name == "gg"
+        assert isinstance(event.emoji_name, emoji_models.UnicodeEmoji)
 
     ################
     # SHARD EVENTS #
diff --git a/tests/hikari/impl/test_rest.py b/tests/hikari/impl/test_rest.py
index d765d128..f99ea9bb 100644
--- a/tests/hikari/impl/test_rest.py
+++ b/tests/hikari/impl/test_rest.py
@@ -669,13 +669,25 @@ def test__get_live_attributes_when_inactive(self, rest_client):
             (emojis.CustomEmoji(id=123, name="rooYay", is_animated=False), "rooYay:123"),
             ("👌", "👌"),
             ("\N{OK HAND SIGN}", "\N{OK HAND SIGN}"),
-            ("<:rooYay:123>", "rooYay:123"),
-            ("<a:rooYay:123>", "rooYay:123"),
-            ("rooYay:123", "rooYay:123"),
+            (emojis.UnicodeEmoji("\N{OK HAND SIGN}"), "\N{OK HAND SIGN}"),
         ],
     )
     def test__transform_emoji_to_url_format(self, rest_client, emoji, expected_return):
-        assert rest_client._transform_emoji_to_url_format(emoji) == expected_return
+        assert rest_client._transform_emoji_to_url_format(emoji, undefined.UNDEFINED) == expected_return
+
+    def test__transform_emoji_to_url_format_with_id(self, rest_client):
+        assert rest_client._transform_emoji_to_url_format("rooYay", 123) == "rooYay:123"
+
+    @pytest.mark.parametrize(  # noqa: PT014 - Duplicate test cases (false positive)
+        "emoji",
+        [
+            emojis.CustomEmoji(id=123, name="rooYay", is_animated=False),
+            emojis.UnicodeEmoji("\N{OK HAND SIGN}"),
+        ],
+    )
+    def test__transform_emoji_to_url_format_when_id_passed_with_emoji_object(self, rest_client, emoji):
+        with pytest.raises(ValueError, match="emoji_id shouldn't be passed when an Emoji object is passed for emoji"):
+            rest_client._transform_emoji_to_url_format(emoji, 123)
 
     def test__stringify_http_message_when_body_is_None(self, rest_client):
         headers = {"HEADER1": "value1", "HEADER2": "value2", "Authorization": "this will never see the light of day"}
@@ -1818,7 +1830,7 @@ async def test_delete_reaction(self, rest_client):
         rest_client._request = mock.AsyncMock()
         rest_client._transform_emoji_to_url_format = mock.Mock(return_value="rooYay:123")
 
-        await rest_client.delete_reaction(StubModel(123), StubModel(456), "<:rooYay:123>", StubModel(789))
+        await rest_client.delete_reaction(StubModel(123), StubModel(456), StubModel(789), "<:rooYay:123>")
         rest_client._request.assert_awaited_once_with(expected_route)
 
     async def test_delete_all_reactions(self, rest_client):
diff --git a/tests/hikari/test_emojis.py b/tests/hikari/test_emojis.py
index 4dfcdc66..afd952a9 100644
--- a/tests/hikari/test_emojis.py
+++ b/tests/hikari/test_emojis.py
@@ -29,16 +29,13 @@ class TestEmoji:
     @pytest.mark.parametrize(
         ("input", "output"),
         [
-            ("12345", emojis.CustomEmoji(id=snowflakes.Snowflake(12345), name=None, is_animated=None)),
             ("<:foo:12345>", emojis.CustomEmoji(id=snowflakes.Snowflake(12345), name="foo", is_animated=False)),
             ("<bar:foo:12345>", emojis.CustomEmoji(id=snowflakes.Snowflake(12345), name="foo", is_animated=False)),
             ("<a:foo:12345>", emojis.CustomEmoji(id=snowflakes.Snowflake(12345), name="foo", is_animated=True)),
-            ("\N{OK HAND SIGN}", emojis.UnicodeEmoji(name="\N{OK HAND SIGN}")),
+            ("\N{OK HAND SIGN}", emojis.UnicodeEmoji("\N{OK HAND SIGN}")),
             (
                 "\N{REGIONAL INDICATOR SYMBOL LETTER G}\N{REGIONAL INDICATOR SYMBOL LETTER B}",
-                emojis.UnicodeEmoji(
-                    name="\N{REGIONAL INDICATOR SYMBOL LETTER G}\N{REGIONAL INDICATOR SYMBOL LETTER B}"
-                ),
+                emojis.UnicodeEmoji("\N{REGIONAL INDICATOR SYMBOL LETTER G}\N{REGIONAL INDICATOR SYMBOL LETTER B}"),
             ),
         ],
     )
@@ -47,18 +44,47 @@ def test_parse(self, input, output):
 
 
 class TestUnicodeEmoji:
-    def test_str_operator(self):
-        assert emojis.UnicodeEmoji(name="\N{OK HAND SIGN}") == "\N{OK HAND SIGN}"
+    @pytest.fixture()
+    def emoji(self):
+        return emojis.UnicodeEmoji("\N{OK HAND SIGN}")
+
+    def test_name_property(self, emoji):
+        assert emoji.name == emoji
+
+    def test_url_name_property(self, emoji):
+        assert emoji.url_name == emoji
+
+    def test_mention_property(self, emoji):
+        assert emoji.mention == emoji
+
+    def test_codepoints_property(self, emoji):
+        assert emoji.codepoints == [128076]
+
+    def test_filename_property(self, emoji):
+        assert emoji.filename == "1f44c.png"
+
+    def test_url_property(self, emoji):
+        assert emoji.url == "https://raw.githubusercontent.com/twitter/twemoji/master/assets/72x72/1f44c.png"
+
+    def test_unicode_escape_property(self, emoji):
+        assert emoji.unicode_escape == "\\U0001f44c"
+
+    def test_parse_codepoints(self, emoji):
+        assert emojis.UnicodeEmoji.parse_codepoints(128076) == emoji
+
+    def test_parse_unicode_escape(self, emoji):
+        assert emojis.UnicodeEmoji.parse_unicode_escape("\\U0001f44c") == emoji
+
+    def test_str_operator(self, emoji):
+        assert str(emoji) == emoji
 
     @pytest.mark.parametrize(
         ("input", "output"),
         [
-            ("\N{OK HAND SIGN}", emojis.UnicodeEmoji(name="\N{OK HAND SIGN}")),
+            ("\N{OK HAND SIGN}", emojis.UnicodeEmoji("\N{OK HAND SIGN}")),
             (
                 "\N{REGIONAL INDICATOR SYMBOL LETTER G}\N{REGIONAL INDICATOR SYMBOL LETTER B}",
-                emojis.UnicodeEmoji(
-                    name="\N{REGIONAL INDICATOR SYMBOL LETTER G}\N{REGIONAL INDICATOR SYMBOL LETTER B}"
-                ),
+                emojis.UnicodeEmoji("\N{REGIONAL INDICATOR SYMBOL LETTER G}\N{REGIONAL INDICATOR SYMBOL LETTER B}"),
             ),
         ],
     )
@@ -67,18 +93,38 @@ def test_parse(self, input, output):
 
 
 class TestCustomEmoji:
+    @pytest.fixture()
+    def emoji(self):
+        return emojis.CustomEmoji(id=3213452, name="ok", is_animated=False)
+
+    def test_filename_property(self, emoji):
+        assert emoji.filename == "3213452.png"
+
+    def test_filename_property_when_animated(self, emoji):
+        emoji.is_animated = True
+        assert emoji.filename == "3213452.gif"
+
+    def test_url_name_property(self, emoji):
+        assert emoji.url_name == "ok:3213452"
+
+    def test_mention_property(self, emoji):
+        assert emoji.mention == "<:ok:3213452>"
+
+    def test_mention_property_when_animated(self, emoji):
+        emoji.is_animated = True
+
+        assert emoji.mention == "<a:ok:3213452>"
+
+    def test_url_property(self, emoji):
+        assert emoji.url == "https://cdn.discordapp.com/emojis/3213452.png"
+
     def test_str_operator_when_populated_name(self):
         emoji = emojis.CustomEmoji(id=snowflakes.Snowflake(12345), name="peepoSad", is_animated=True)
-        assert str(emoji) == "peepoSad"
-
-    def test_str_operator_when_name_is_None(self):
-        emoji = emojis.CustomEmoji(id=snowflakes.Snowflake(12345), name=None, is_animated=True)
-        assert str(emoji) == "Unnamed emoji ID 12345"
+        assert str(emoji) == emoji.mention
 
     @pytest.mark.parametrize(
         ("input", "output"),
         [
-            ("12345", emojis.CustomEmoji(id=snowflakes.Snowflake(12345), name=None, is_animated=None)),
             ("<:foo:12345>", emojis.CustomEmoji(id=snowflakes.Snowflake(12345), name="foo", is_animated=False)),
             ("<bar:foo:12345>", emojis.CustomEmoji(id=snowflakes.Snowflake(12345), name="foo", is_animated=False)),
             ("<a:foo:12345>", emojis.CustomEmoji(id=snowflakes.Snowflake(12345), name="foo", is_animated=True)),
@@ -88,5 +134,5 @@ def test_parse(self, input, output):
         assert emojis.CustomEmoji.parse(input) == output
 
     def test_parse_unhappy_path(self):
-        with pytest.raises(ValueError, match="Expected an emoji ID or emoji mention"):
+        with pytest.raises(ValueError, match="Expected an emoji mention"):
             emojis.CustomEmoji.parse("xxx")
diff --git a/tests/hikari/test_messages.py b/tests/hikari/test_messages.py
index 907b9cf2..9afab863 100644
--- a/tests/hikari/test_messages.py
+++ b/tests/hikari/test_messages.py
@@ -310,23 +310,27 @@ async def test_add_reaction(self, message):
         message.app = mock.AsyncMock()
         message.id = 123
         message.channel_id = 456
-        await message.add_reaction("👌")
-        message.app.rest.add_reaction.assert_awaited_once_with(channel=456, message=123, emoji="👌")
+        await message.add_reaction("👌", 123123)
+        message.app.rest.add_reaction.assert_awaited_once_with(channel=456, message=123, emoji="👌", emoji_id=123123)
 
     async def test_remove_reaction(self, message):
         message.app = mock.AsyncMock()
         message.id = 123
         message.channel_id = 456
-        await message.remove_reaction("👌")
-        message.app.rest.delete_my_reaction.assert_awaited_once_with(channel=456, message=123, emoji="👌")
+        await message.remove_reaction("👌", 341231)
+        message.app.rest.delete_my_reaction.assert_awaited_once_with(
+            channel=456, message=123, emoji="👌", emoji_id=341231
+        )
 
     async def test_remove_reaction_with_user(self, message):
         message.app = mock.AsyncMock()
         user = object()
         message.id = 123
         message.channel_id = 456
-        await message.remove_reaction("👌", user=user)
-        message.app.rest.delete_reaction.assert_awaited_once_with(channel=456, message=123, emoji="👌", user=user)
+        await message.remove_reaction("👌", 31231, user=user)
+        message.app.rest.delete_reaction.assert_awaited_once_with(
+            channel=456, message=123, emoji="👌", emoji_id=31231, user=user
+        )
 
     async def test_remove_all_reactions(self, message):
         message.app = mock.AsyncMock()
@@ -339,5 +343,7 @@ async def test_remove_all_reactions_with_emoji(self, message):
         message.app = mock.AsyncMock()
         message.id = 123
         message.channel_id = 456
-        await message.remove_all_reactions("👌")
-        message.app.rest.delete_all_reactions_for_emoji.assert_awaited_once_with(channel=456, message=123, emoji="👌")
+        await message.remove_all_reactions("👌", emoji_id=65655)
+        message.app.rest.delete_all_reactions_for_emoji.assert_awaited_once_with(
+            channel=456, message=123, emoji="👌", emoji_id=65655
+        )