Repository: gpauloski/3pseatBot

Issue:
`admin_or_owner` is not working for owner part.
### Describe the problem.

Admins can run commands decorated with `@app_commands.check(admin_or_owner)` but it does not work for the bot owner that is not an admin in that guild.

Maybe the interaction client is not a bot? https://github.com/gpauloski/3pseatBot/blob/0387011622b28b39771db2fe4fb2fc821255436e/threepseat/commands/commands.py#L98

### (Optional) How did you install and run 3pseatBot?

_No response_

### (Optional) What version of 3pseatBot are you using?

v2.0.3

Discussion:

Patch:
diff --git a/threepseat/commands/commands.py b/threepseat/commands/commands.py
index ec712f7..2b14d02 100644
--- a/threepseat/commands/commands.py
+++ b/threepseat/commands/commands.py
@@ -94,10 +94,10 @@ async def admin_or_owner(interaction: discord.Interaction) -> bool:
         >>> @app_commands.check(admin_or_owner)
         >>> def mycommand(...): ...
     """
-    if (
-        isinstance(interaction.client, commands.Bot)
-        and interaction.user.id == interaction.client.owner_id
-    ):
+    if isinstance(
+        interaction.client,
+        commands.Bot,
+    ) and await interaction.client.is_owner(interaction.user):
         return True
     elif (
         isinstance(interaction.user, discord.Member)
diff --git a/threepseat/commands/custom.py b/threepseat/commands/custom.py
index 1f54d63..363b6ae 100644
--- a/threepseat/commands/custom.py
+++ b/threepseat/commands/custom.py
@@ -138,6 +138,16 @@ class CustomCommands(app_commands.Group):
                 command._asdict(),
             )
 
+    def exists_in_db(self, guild_id: int, name: str) -> bool:
+        """Check if guild has a command with name."""
+        with self.connect() as db:
+            count = db.execute(
+                'SELECT COUNT(*) FROM custom_commands '
+                'WHERE guild_id = :guild_id AND name = :name',
+                {'guild_id': guild_id, 'name': name},
+            ).fetchone()[0]
+        return bool(count)
+
     def list_in_db(self, guild_id: int | None = None) -> list[CustomCommand]:
         """Get list of custom commands in guild."""
         with self.connect() as db:
@@ -212,7 +222,7 @@ class CustomCommands(app_commands.Group):
         self.add_to_db(command)
         await self.register(command, interaction.client)
 
-        await interaction.followup.send(f'Created /{name}')
+        await interaction.followup.send(f'Created /{name}.', ephemeral=True)
 
     @app_commands.command(name='list', description='List custom commands')
     @app_commands.check(log_interaction)
@@ -224,6 +234,7 @@ class CustomCommands(app_commands.Group):
         if len(commands) == 0:
             await interaction.response.send_message(
                 'The guild has no custom commands.',
+                ephemeral=True,
             )
         else:
             names = [command.name for command in commands]
@@ -231,6 +242,7 @@ class CustomCommands(app_commands.Group):
 
             await interaction.response.send_message(
                 f'This guild has these custom commands: {name_list}.',
+                ephemeral=True,
             )
 
     @app_commands.command(
@@ -250,10 +262,19 @@ class CustomCommands(app_commands.Group):
         await interaction.response.defer(thinking=True)
 
         assert interaction.guild is not None
-        self.remove_from_db(name, interaction.guild.id)
-        await self.unregister(name, interaction.guild, interaction.client)
 
-        await interaction.followup.send(f'Removed /{name}')
+        if self.exists_in_db(interaction.guild.id, name):
+            self.remove_from_db(name, interaction.guild.id)
+            await self.unregister(name, interaction.guild, interaction.client)
+            await interaction.followup.send(
+                f'Removed /{name}.',
+                ephemeral=True,
+            )
+        else:
+            await interaction.followup.send(
+                f'The command /{name} does not exist.',
+                ephemeral=True,
+            )
 
     async def on_error(
         self,
diff --git a/threepseat/sounds/commands.py b/threepseat/sounds/commands.py
index df5b6ec..73ffc0c 100644
--- a/threepseat/sounds/commands.py
+++ b/threepseat/sounds/commands.py
@@ -173,12 +173,18 @@ class SoundCommands(app_commands.Group):
     ) -> None:
         """Remove a sound."""
         assert interaction.guild is not None
-        self.sounds.remove(name, interaction.guild.id)
-        self.sounds_list_cached.cache_clear()
-
-        await interaction.response.send_message(
-            f'Removed *{name}* if it existed.',
-        )
+        sound = self.sounds.get(name, guild_id=interaction.guild.id)
+        if sound is None:
+            await interaction.response.send_message(
+                f'A sound named *{name}* does not exist.',
+                ephemeral=True,
+            )
+        else:
+            self.sounds.remove(name, interaction.guild.id)
+            self.sounds_list_cached.cache_clear()
+            await interaction.response.send_message(
+                f'Removed the *{name}* sound.',
+            )
 
     async def on_error(
         self,
diff --git a/threepseat/sounds/templates/sounds.html b/threepseat/sounds/templates/sounds.html
index 5c9cf00..020a0be 100644
--- a/threepseat/sounds/templates/sounds.html
+++ b/threepseat/sounds/templates/sounds.html
@@ -75,7 +75,7 @@
       {% for sound in sounds %}
       <div class="card blue-grey darken-2" style="margin: 0;">
         <div class="card-content blue-grey-text text-lighten-4">
-          <span class="card-title activator blue-grey-text text-lighten-4">
+          <span class="card-title blue-grey-text text-lighten-4">
             <b>{{ sound.name }}</b>
           </span>
           <p>{{ sound.description }}</p>


Test patch:
diff --git a/tests/commands/custom_test.py b/tests/commands/custom_test.py
index 35513ef..d6fb8a5 100644
--- a/tests/commands/custom_test.py
+++ b/tests/commands/custom_test.py
@@ -139,17 +139,45 @@ async def test_remove(mockbot: Bot) -> None:
     assert custom is not None
     remove_ = extract(custom.remove)
 
+    command = CustomCommand(
+        name='mycommand',
+        description='a command',
+        body='this is my command',
+        author_id=1234,
+        guild_id=5678,
+        creation_time=0,
+    )
+    custom.add_to_db(command)
+
     interaction = MockInteraction(
         custom.remove,
         user='calling-user',
         channel='mychannel',
-        guild='myguild',
+        guild=MockGuild('guild', 5678),
         client=mockbot,
     )
 
-    await remove_(custom, interaction, 'command1')
+    await remove_(custom, interaction, 'mycommand')
     assert interaction.followed
-    assert interaction.followup_message is not None
+    assert (
+        interaction.followup_message is not None
+        and 'Removed' in interaction.followup_message
+    )
+
+    interaction = MockInteraction(
+        custom.remove,
+        user='calling-user',
+        channel='mychannel',
+        guild=MockGuild('guild', 5678),
+        client=mockbot,
+    )
+
+    await remove_(custom, interaction, 'mycommand')
+    assert interaction.followed
+    assert (
+        interaction.followup_message is not None
+        and 'does not exist' in interaction.followup_message
+    )
 
 
 @pytest.mark.asyncio
@@ -265,6 +293,22 @@ def test_db_remove_command(tmp_file: str) -> None:
     assert len(rows) == 0
 
 
+def test_exists_in_db(tmp_file: str) -> None:
+    custom = CustomCommands(tmp_file)
+
+    assert not custom.exists_in_db(5678, 'mycommand')
+    command = CustomCommand(
+        name='mycommand',
+        description='a command',
+        body='this is my command',
+        author_id=1234,
+        guild_id=5678,
+        creation_time=0,
+    )
+    custom.add_to_db(command)
+    assert custom.exists_in_db(5678, 'mycommand')
+
+
 @pytest.mark.asyncio
 async def test_autocomplete(tmp_file: str) -> None:
     custom = CustomCommands(tmp_file)
diff --git a/tests/sounds/commands_test.py b/tests/sounds/commands_test.py
index 31d12b7..7bd6d98 100644
--- a/tests/sounds/commands_test.py
+++ b/tests/sounds/commands_test.py
@@ -406,7 +406,7 @@ async def test_remove_command_missing(mockbot: Bot) -> None:
     assert interaction.responded
     assert (
         interaction.response_message is not None
-        and 'Removed' in interaction.response_message
+        and 'does not exist' in interaction.response_message
     )