Repository: hikari-py/hikari
Pull request #686 (2021-06-30 08:37:42)

Improve typing/strictness of Emoji classes
### Summary
Improve typing of Emoji classes
* Make CustomEmoji (and therefore also Emoji) more strict
* Flatten down reaction event models and guild WelcomeChannel + document special case behaviour on them directly.
* Remove Emoji.is_mentionable as it is no longer relevant.

This also removes cases where CustomEmoji.filename, CustomEmoji.mention, CustomEmoji.url_name and CustomEmoji.url would return potentially invalid data if a field was None.

* For CustomEmoji.mention there was a partner CustomEmoji.is_mentionable field which was probably intended to indicate whether CustomEmoji.mention would return data which is guaranteed to be valid or not but this behaviour wasn't documented.
* When CustomEmoji.url_name would return `"None:{emoji.id}"` while this technically works under current behaviour (as the emoji name is currently ignored for custom emojis in the reaction endpoint paths) this behaviour isn't guaranteed and may break down the line (see https://github.com/discord/discord-api-docs/issues/2653#issuecomment-786276283 ).

This also fixes a bug where a WelcomeChannel with emoji_id set but emoji_name left as null would be treated as if it doesn't have any emoji set during deserialization

### 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.
- [x] I have made unittests according to the code I have added/modified/deleted.

### Related issues
<!--
To mention an issue use `#issue-id` and to mention a merge request use `!merge-request-id`
To close/fix an issue use `Close #issue-id` or `Fix #issue-id` (depending on the merge request)
-->



Patch:
diff --git a/.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/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/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/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/cache.py b/hikari/internal/cache.py
index da981568..575da5bd 100644
--- a/hikari/internal/cache.py
+++ b/hikari/internal/cache.py
@@ -453,7 +453,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()
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
+        )