From 442ad40ab23201f96c37c77e15bbacaf9dd385c1 Mon Sep 17 00:00:00 2001
From: z03h <7235242+z03h@users.noreply.github.com>
Date: Sat, 12 Oct 2024 23:49:50 -0700
Subject: [PATCH] [commands] Add SoundboardSoundConverter

---
 discord/ext/commands/converter.py | 40 +++++++++++++++++++++++++++++++
 discord/ext/commands/errors.py    | 19 +++++++++++++++
 docs/ext/commands/api.rst         |  4 ++++
 3 files changed, 63 insertions(+)

diff --git a/discord/ext/commands/converter.py b/discord/ext/commands/converter.py
index 6c559009d..744a00fd3 100644
--- a/discord/ext/commands/converter.py
+++ b/discord/ext/commands/converter.py
@@ -82,6 +82,7 @@ __all__ = (
     'GuildChannelConverter',
     'GuildStickerConverter',
     'ScheduledEventConverter',
+    'SoundboardSoundConverter',
     'clean_content',
     'Greedy',
     'Range',
@@ -951,6 +952,44 @@ class ScheduledEventConverter(IDConverter[discord.ScheduledEvent]):
         return result
 
 
+class SoundboardSoundConverter(IDConverter[discord.SoundboardSound]):
+    """Converts to a :class:`~discord.SoundboardSound`.
+
+    Lookups are done for the local guild if available. Otherwise, for a DM context,
+    lookup is done by the global cache.
+
+    The lookup strategy is as follows (in order):
+
+    1. Lookup by ID.
+    2. Lookup by name.
+
+    .. versionadded:: 2.5
+    """
+
+    async def convert(self, ctx: Context[BotT], argument: str) -> discord.SoundboardSound:
+        guild = ctx.guild
+        match = self._get_id_match(argument)
+        result = None
+
+        if match:
+            # ID match
+            sound_id = int(match.group(1))
+            if guild:
+                result = guild.get_soundboard_sound(sound_id)
+            else:
+                result = ctx.bot.get_soundboard_sound(sound_id)
+        else:
+            # lookup by name
+            if guild:
+                result = discord.utils.get(guild.soundboard_sounds, name=argument)
+            else:
+                result = discord.utils.get(ctx.bot.soundboard_sounds, name=argument)
+        if result is None:
+            raise SoundboardSoundNotFound(argument)
+
+        return result
+
+
 class clean_content(Converter[str]):
     """Converts the argument to mention scrubbed version of
     said content.
@@ -1263,6 +1302,7 @@ CONVERTER_MAPPING: Dict[type, Any] = {
     discord.GuildSticker: GuildStickerConverter,
     discord.ScheduledEvent: ScheduledEventConverter,
     discord.ForumChannel: ForumChannelConverter,
+    discord.SoundboardSound: SoundboardSoundConverter,
 }
 
 
diff --git a/discord/ext/commands/errors.py b/discord/ext/commands/errors.py
index 0c1e0f2d0..0c3cfa0c4 100644
--- a/discord/ext/commands/errors.py
+++ b/discord/ext/commands/errors.py
@@ -75,6 +75,7 @@ __all__ = (
     'EmojiNotFound',
     'GuildStickerNotFound',
     'ScheduledEventNotFound',
+    'SoundboardSoundNotFound',
     'PartialEmojiConversionFailure',
     'BadBoolArgument',
     'MissingRole',
@@ -564,6 +565,24 @@ class ScheduledEventNotFound(BadArgument):
         super().__init__(f'ScheduledEvent "{argument}" not found.')
 
 
+class SoundboardSoundNotFound(BadArgument):
+    """Exception raised when the bot can not find the soundboard sound.
+
+    This inherits from :exc:`BadArgument`
+
+    .. versionadded:: 2.5
+
+    Attributes
+    -----------
+    argument: :class:`str`
+        The sound supplied by the caller that was not found
+    """
+
+    def __init__(self, argument: str) -> None:
+        self.argument: str = argument
+        super().__init__(f'SoundboardSound "{argument}" not found.')
+
+
 class BadBoolArgument(BadArgument):
     """Exception raised when a boolean argument was not convertable.
 
diff --git a/docs/ext/commands/api.rst b/docs/ext/commands/api.rst
index 9bda24f6e..f55225614 100644
--- a/docs/ext/commands/api.rst
+++ b/docs/ext/commands/api.rst
@@ -708,6 +708,9 @@ Exceptions
 .. autoexception:: discord.ext.commands.ScheduledEventNotFound
     :members:
 
+.. autoexception:: discord.ext.commands.SoundboardSoundNotFound
+    :members:
+
 .. autoexception:: discord.ext.commands.BadBoolArgument
     :members:
 
@@ -800,6 +803,7 @@ Exception Hierarchy
                     - :exc:`~.commands.EmojiNotFound`
                     - :exc:`~.commands.GuildStickerNotFound`
                     - :exc:`~.commands.ScheduledEventNotFound`
+                    - :exc:`~.commands.SoundboardSoundNotFound`
                     - :exc:`~.commands.PartialEmojiConversionFailure`
                     - :exc:`~.commands.BadBoolArgument`
                     - :exc:`~.commands.RangeError`