Repository: Pycord-Development/pycord

Issue:
Setting the emoji property of a SelectOption after initialization raises an exception when sending it
### Summary

Setting the emoji property of a SelectOption after initialization raises an exception when sending it

### Reproduction Steps

- Set the emoji property of a SelectOption to `"🔴"` after initializing the SelectOption

### Minimal Reproducible Code

```python
from discord import slash_command, ApplicationContext, SelectOption
from discord.ext.commands import Cog
from discord.ui import View, Select


class CustomSelectOption(SelectOption):
    def __init__(self):
        super().__init__(label="Testing")
        self.emoji = "🔴"


class CustomSelect(Select):
    def __init__(self):
        super().__init__(options=[CustomSelectOption()])


class CustomView(View):
    def __init__(self):
        super().__init__()
        self.add_item(CustomSelect())


class TestCog(Cog):
    def __init__(self, bot):
        self.bot = bot

    @slash_command()
    async def test(self, ctx: ApplicationContext):
        view = CustomView()
        await ctx.respond(view=view)
```


### Expected Results

No exception.

### Actual Results

An exception similar to this
```
Ignoring exception in command test:
Traceback (most recent call last):
  File "D:\Proyectos\Discord\BotTesting\venv\lib\site-packages\discord\commands\core.py", line 127, in wrapped
    ret = await coro(arg)
  File "D:\Proyectos\Discord\BotTesting\venv\lib\site-packages\discord\commands\core.py", line 877, in _invoke
    await self.callback(self.cog, ctx, **kwargs)
  File "D:\Proyectos\Discord\BotTesting\bot\cog.py", line 30, in test
    await ctx.respond(view=view)
  File "D:\Proyectos\Discord\BotTesting\venv\lib\site-packages\discord\commands\context.py", line 258, in respond
    return await self.interaction.response.send_message(*args, **kwargs)  # self.response
  File "D:\Proyectos\Discord\BotTesting\venv\lib\site-packages\discord\interactions.py", line 680, in send_message
    payload["components"] = view.to_components()
  File "D:\Proyectos\Discord\BotTesting\venv\lib\site-packages\discord\ui\view.py", line 216, in to_components
    children = [item.to_component_dict() for item in group]
  File "D:\Proyectos\Discord\BotTesting\venv\lib\site-packages\discord\ui\view.py", line 216, in <listcomp>
    children = [item.to_component_dict() for item in group]
  File "D:\Proyectos\Discord\BotTesting\venv\lib\site-packages\discord\ui\select.py", line 280, in to_component_dict
    return self._underlying.to_dict()
  File "D:\Proyectos\Discord\BotTesting\venv\lib\site-packages\discord\components.py", line 348, in to_dict
    "options": [op.to_dict() for op in self.options],
  File "D:\Proyectos\Discord\BotTesting\venv\lib\site-packages\discord\components.py", line 348, in <listcomp>
    "options": [op.to_dict() for op in self.options],
  File "D:\Proyectos\Discord\BotTesting\venv\lib\site-packages\discord\components.py", line 456, in to_dict
    payload["emoji"] = self.emoji.to_dict()  # type: ignore
AttributeError: 'str' object has no attribute 'to_dict'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
  File "D:\Proyectos\Discord\BotTesting\venv\lib\site-packages\discord\bot.py", line 992, in invoke_application_command
    await ctx.command.invoke(ctx)
  File "D:\Proyectos\Discord\BotTesting\venv\lib\site-packages\discord\commands\core.py", line 358, in invoke
    await injected(ctx)
  File "D:\Proyectos\Discord\BotTesting\venv\lib\site-packages\discord\commands\core.py", line 135, in wrapped
    raise ApplicationCommandInvokeError(exc) from exc
discord.errors.ApplicationCommandInvokeError: Application Command raised an exception: AttributeError: 'str' object has no attribute 'to_dict
```

### Intents

Default Intents

### System Information

- Python v3.9.10-final
- py-cord v2.0.0-final
- aiohttp v3.8.1
- system info: Windows 10 10.0.19044

### Checklist

- [X] I have searched the open issues for duplicates.
- [X] I have shown the entire traceback, if possible.
- [X] I have removed my token from display, if visible.

### Additional Context

On initialization, the following code is executed:

```py
        if emoji is not None:
            if isinstance(emoji, str):
                emoji = PartialEmoji.from_str(emoji)
            elif isinstance(emoji, _EmojiTag):
                emoji = emoji._to_partial()
            else:
                raise TypeError(f"expected emoji to be str, Emoji, or PartialEmoji not {emoji.__class__}")
```

Which is not executed when doing `the_select_option.emoji = "🔴"`.


Discussion:

Resolving patch:
diff --git a/discord/components.py b/discord/components.py
--- a/discord/components.py
+++ b/discord/components.py
@@ -374,8 +374,6 @@ class SelectOption:
     description: Optional[:class:`str`]
         An additional description of the option, if any.
         Can only be up to 100 characters.
-    emoji: Optional[Union[:class:`str`, :class:`Emoji`, :class:`PartialEmoji`]]
-        The emoji of the option, if available.
     default: :class:`bool`
         Whether this option is selected by default.
     """
@@ -384,7 +382,7 @@ class SelectOption:
         "label",
         "value",
         "description",
-        "emoji",
+        "_emoji",
         "default",
     )
 
@@ -399,22 +397,16 @@ def __init__(
     ) -> None:
         if len(label) > 100:
             raise ValueError("label must be 100 characters or fewer")
+
         if value is not MISSING and len(value) > 100:
             raise ValueError("value must be 100 characters or fewer")
+
         if description is not None and len(description) > 100:
             raise ValueError("description must be 100 characters or fewer")
+
         self.label = label
         self.value = label if value is MISSING else value
         self.description = description
-
-        if emoji is not None:
-            if isinstance(emoji, str):
-                emoji = PartialEmoji.from_str(emoji)
-            elif isinstance(emoji, _EmojiTag):
-                emoji = emoji._to_partial()
-            else:
-                raise TypeError(f"expected emoji to be str, Emoji, or PartialEmoji not {emoji.__class__}")
-
         self.emoji = emoji
         self.default = default
 
@@ -430,6 +422,23 @@ def __str__(self) -> str:
             return f"{base}\n{self.description}"
         return base
 
+    @property
+    def emoji(self) -> Optional[Union[str, Emoji, PartialEmoji]]:
+        """Optional[Union[:class:`str`, :class:`Emoji`, :class:`PartialEmoji`]]: The emoji of the option, if available."""
+        return self._emoji
+
+    @emoji.setter
+    def emoji(self, value) -> None:
+        if value is not None:
+            if isinstance(value, str):
+                value = PartialEmoji.from_str(value)
+            elif isinstance(value, _EmojiTag):
+                value = value._to_partial()
+            else:
+                raise TypeError(f"expected emoji to be str, Emoji, or PartialEmoji not {value.__class__}")
+
+        self._emoji = value
+
     @classmethod
     def from_dict(cls, data: SelectOptionPayload) -> SelectOption:
         try:


Test patch: