From 07aca60c6dbd4606eb090e800bdec8e991509e2c Mon Sep 17 00:00:00 2001 From: Marcin Macierzanka Date: Fri, 21 Aug 2026 18:10:22 +0200 Subject: [PATCH] refactor: move Discord theme into bot package --- README.md | 2 + zyrarbot/music/track_embed_factory.py | 5 +- zyrarbot/ui/bot/cogs/common.py | 11 +++- zyrarbot/ui/bot/cogs/control_cog.py | 36 +++++++---- zyrarbot/ui/bot/cogs/playback_cog.py | 89 +++++++++++++++++++++++---- zyrarbot/ui/bot/cogs/player_cog.py | 22 +++++-- zyrarbot/ui/bot/cogs/queue_cog.py | 38 +++++++++--- zyrarbot/ui/bot/cogs/settings_cog.py | 14 +++-- zyrarbot/ui/bot/cogs/voice_cog.py | 19 ++++-- zyrarbot/ui/bot/discord_theme.py | 12 ++++ 10 files changed, 194 insertions(+), 54 deletions(-) create mode 100644 zyrarbot/ui/bot/discord_theme.py diff --git a/README.md b/README.md index a4447e8..1aaa127 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ indeksów ręcznie. | `/player` | Publiczny panel odtwarzacza z przyciskami sterowania | | `/play query` | Dodaje utwór, link lub zapytanie wyszukiwania | | `/playfile filename` | Odtwarza plik z biblioteki serwera | +| `/files` | Otwiera listę plików z natywnym wyborem Discorda | | `/upload file` | Przesyła i waliduje plik audio/wideo do biblioteki | | `/deletefile filename` | Usuwa wybrany plik z biblioteki | | `/replay track_id` | Ponownie odtwarza utwór z historii | @@ -105,6 +106,7 @@ zyrarbot/ │ └── routers/ trasy pogrupowane tematycznie └── bot/ Warstwa Discorda ├── client.py MusicBot + ├── discord_theme.py wspólny styl embedów Discorda ├── guild_resolver.py GuildResolver └── cogs/ komendy i listenery Discorda ├── common.py wspólne zależności i helpery diff --git a/zyrarbot/music/track_embed_factory.py b/zyrarbot/music/track_embed_factory.py index 478962d..0ef504d 100644 --- a/zyrarbot/music/track_embed_factory.py +++ b/zyrarbot/music/track_embed_factory.py @@ -3,6 +3,7 @@ from __future__ import annotations import discord from zyrarbot.audio.ytdl_source import YTDLSource +from zyrarbot.ui.bot.discord_theme import BRAND_PURPLE AUTHOR_ICON = "https://cdn-icons-png.flaticon.com/512/3844/3844724.png" @@ -11,14 +12,14 @@ class TrackEmbedFactory: """Buduje osadzenia Discorda opisujące utwór dodany do kolejki lub odtwarzany.""" def queued(self, track: YTDLSource, *, position: int, from_web: bool) -> discord.Embed: - embed = self._base(track, discord.Color.blue()) + embed = self._base(track, BRAND_PURPLE) source = "Dodano do kolejki z panelu Web" if from_web else "Dodano do kolejki" embed.set_author(name=source, icon_url=AUTHOR_ICON) embed.set_footer(text=f"Pozycja w kolejce: {position}") return embed def now_playing(self, track: YTDLSource, *, position: int, total: int) -> discord.Embed: - embed = self._base(track, discord.Color.green(), prefix="▶️") + embed = self._base(track, BRAND_PURPLE, prefix="▶️") embed.set_footer(text=f"Pozycja w kolejce: {position} / {total}") return embed diff --git a/zyrarbot/ui/bot/cogs/common.py b/zyrarbot/ui/bot/cogs/common.py index 94940f9..7d55496 100644 --- a/zyrarbot/ui/bot/cogs/common.py +++ b/zyrarbot/ui/bot/cogs/common.py @@ -4,6 +4,7 @@ import discord from discord.ext import commands from zyrarbot.music.music_player import MusicPlayer +from zyrarbot.ui.bot.discord_theme import message_embed class MusicCogBase(commands.Cog): @@ -16,7 +17,8 @@ class MusicCogBase(commands.Cog): async def _ensure_voice(self, interaction: discord.Interaction) -> bool: if not interaction.user.voice: await interaction.response.send_message( - "Nie jesteś połączony z kanałem głosowym.", ephemeral=True + embed=message_embed("Kanał głosowy", "Nie jesteś połączony z kanałem głosowym."), + ephemeral=True, ) return False @@ -25,7 +27,10 @@ class MusicCogBase(commands.Cog): await interaction.user.voice.channel.connect() elif voice_client.channel != interaction.user.voice.channel: await interaction.response.send_message( - "Jestem już połączony z innym kanałem głosowym.", ephemeral=True + embed=message_embed( + "Kanał głosowy", "Jestem już połączony z innym kanałem głosowym." + ), + ephemeral=True, ) return False return True @@ -36,4 +41,4 @@ class MusicCogBase(commands.Cog): result = await self.player.enqueue(interaction.guild.id, track, channel=interaction.channel) title = discord.utils.escape_markdown(track.title or "Nieznany tytuł") action = "Dodano do kolejki" if result["status"] == "queued" else "Teraz odtwarzane" - await interaction.followup.send(f"{action}{prefix}: {title}") + await interaction.followup.send(embed=message_embed(f"{action}{prefix}", title)) diff --git a/zyrarbot/ui/bot/cogs/control_cog.py b/zyrarbot/ui/bot/cogs/control_cog.py index 7c56d69..344b9db 100644 --- a/zyrarbot/ui/bot/cogs/control_cog.py +++ b/zyrarbot/ui/bot/cogs/control_cog.py @@ -5,6 +5,7 @@ from discord import app_commands from zyrarbot.music.loop_mode import LoopMode from zyrarbot.ui.bot.cogs.common import MusicCogBase +from zyrarbot.ui.bot.discord_theme import message_embed class ControlCog(MusicCogBase, name="Control"): @@ -17,33 +18,38 @@ class ControlCog(MusicCogBase, name="Control"): if state.loop_mode.loops_track: state.loop_mode = LoopMode.OFF if not self.player.is_active(guild_id): - await interaction.response.send_message("Nic nie jest odtwarzane.", ephemeral=True) + await interaction.response.send_message( + embed=message_embed("Odtwarzanie", "Nic nie jest odtwarzane."), ephemeral=True + ) return self.player.skip(guild_id) await self.player.trigger_state_change() - await interaction.response.send_message("Pominięto aktualny utwór.") + await interaction.response.send_message(embed=message_embed("Pominięto utwór", "⏭️")) @app_commands.command(name="pause", description="Wstrzymaj odtwarzanie") async def pause(self, interaction: discord.Interaction) -> None: voice_client = interaction.guild.voice_client if not voice_client or not voice_client.is_playing(): - await interaction.response.send_message("Nic nie jest odtwarzane.", ephemeral=True) + await interaction.response.send_message( + embed=message_embed("Odtwarzanie", "Nic nie jest odtwarzane."), ephemeral=True + ) return voice_client.pause() await self.player.trigger_state_change() - await interaction.response.send_message("Odtwarzanie wstrzymane.") + await interaction.response.send_message(embed=message_embed("Odtwarzanie", "Wstrzymano.")) @app_commands.command(name="resume", description="Wznów odtwarzanie") async def resume(self, interaction: discord.Interaction) -> None: voice_client = interaction.guild.voice_client if not voice_client or not voice_client.is_paused(): await interaction.response.send_message( - "Odtwarzanie nie jest wstrzymane.", ephemeral=True + embed=message_embed("Odtwarzanie", "Odtwarzanie nie jest wstrzymane."), + ephemeral=True, ) return voice_client.resume() await self.player.trigger_state_change() - await interaction.response.send_message("Odtwarzanie wznowione.") + await interaction.response.send_message(embed=message_embed("Odtwarzanie", "Wznowiono.")) @app_commands.command(name="stop", description="Zatrzymaj odtwarzanie i wyczyść kolejkę") async def stop(self, interaction: discord.Interaction) -> None: @@ -54,32 +60,38 @@ class ControlCog(MusicCogBase, name="Control"): voice_client.stop() self.player.clear(guild_id) await self.player.trigger_state_change() - await interaction.response.send_message("Zatrzymano odtwarzanie i wyczyszczono kolejkę.") + await interaction.response.send_message( + embed=message_embed("Odtwarzanie zatrzymane", "Kolejka została wyczyszczona.") + ) @app_commands.command(name="volume", description="Zmień głośność odtwarzacza") @app_commands.describe(volume="Poziom głośności (1-100)") async def volume(self, interaction: discord.Interaction, volume: int) -> None: if interaction.guild.voice_client is None: await interaction.response.send_message( - "Nie połączono z kanałem głosowym.", ephemeral=True + embed=message_embed("Głośność", "Nie połączono z kanałem głosowym."), ephemeral=True ) return if not 0 < volume <= 100: await interaction.response.send_message( - "Wprowadź wartość z przedziału od 1 do 100.", ephemeral=True + embed=message_embed("Głośność", "Wprowadź wartość z przedziału od 1 do 100."), + ephemeral=True, ) return self.player.set_volume(interaction.guild.id, volume / 100) await self.player.trigger_state_change() - await interaction.response.send_message(f"Zmieniono głośność na {volume}%") + await interaction.response.send_message( + embed=message_embed("Głośność", f"Ustawiono głośność na {volume}%.") + ) @app_commands.command(name="nowplaying", description="Pokaż aktualnie odtwarzany utwór") async def now_playing(self, interaction: discord.Interaction) -> None: current = self.player.state(interaction.guild.id).current if not current: await interaction.response.send_message( - "Nic nie jest obecnie odtwarzane.", ephemeral=True + embed=message_embed("Teraz odtwarzane", "Nic nie jest obecnie odtwarzane."), + ephemeral=True, ) return title = discord.utils.escape_markdown(current.title or "") - await interaction.response.send_message(f"Teraz odtwarzane: {title}") + await interaction.response.send_message(embed=message_embed("Teraz odtwarzane", title)) diff --git a/zyrarbot/ui/bot/cogs/playback_cog.py b/zyrarbot/ui/bot/cogs/playback_cog.py index 38bfaf0..dca7870 100644 --- a/zyrarbot/ui/bot/cogs/playback_cog.py +++ b/zyrarbot/ui/bot/cogs/playback_cog.py @@ -13,6 +13,7 @@ from zyrarbot.audio.track_factory import TrackFactory from zyrarbot.music.music_player import MusicPlayer from zyrarbot.storage.uploads_repository import UploadsRepository from zyrarbot.ui.bot.cogs.common import MusicCogBase +from zyrarbot.ui.bot.discord_theme import BRAND_PURPLE, message_embed logger = logging.getLogger(__name__) AUTOCOMPLETE_LIMIT = 25 @@ -41,6 +42,25 @@ class HistorySelect(discord.ui.Select): await view.cog.replay_entry(interaction, self.values[0]) +class FileView(discord.ui.View): + def __init__(self, cog: PlaybackCog, guild_id: int): + super().__init__(timeout=900) + self.cog = cog + files = cog._uploads.list(guild_id)[:25] + self.add_item(FileSelect([item.filename for item in files])) + + +class FileSelect(discord.ui.Select): + def __init__(self, filenames: list[str]): + options = [discord.SelectOption(label=name[:100], value=name) for name in filenames] + super().__init__(placeholder="Wybierz plik do odtworzenia", options=options) + + async def callback(self, interaction: discord.Interaction) -> None: + view = self.view + if isinstance(view, FileView): + await view.cog.play_filename(interaction, self.values[0]) + + class PlaybackCog(MusicCogBase, name="Playback"): """Komendy dodawania i ponownego odtwarzania utworów.""" @@ -63,13 +83,29 @@ class PlaybackCog(MusicCogBase, name="Playback"): ) return [app_commands.Choice(name=name, value=name) for name in names] + @app_commands.command(name="files", description="Wybierz plik z biblioteki do odtworzenia") + async def files(self, interaction: discord.Interaction) -> None: + if not self._uploads.list(interaction.guild.id): + await interaction.response.send_message( + embed=message_embed("Biblioteka plików", "Brak plików na tym serwerze."), + ephemeral=True, + ) + return + await interaction.response.send_message( + embed=message_embed("Biblioteka plików", "Wybierz plik z listy poniżej."), + view=FileView(self, interaction.guild.id), + ) + @app_commands.command(name="upload", description="Prześlij plik audio lub wideo do biblioteki") @app_commands.describe(file="Plik multimedialny") async def upload(self, interaction: discord.Interaction, file: discord.Attachment) -> None: filename = file.filename or "" if not self._uploads.is_supported_filename(filename): await interaction.response.send_message( - "❌ Obsługiwane są tylko pliki audio i wideo.", ephemeral=True + embed=message_embed( + "Przesyłanie pliku", "Obsługiwane są tylko pliki audio i wideo." + ), + ephemeral=True, ) return suffix = os.path.splitext(filename)[1] @@ -77,21 +113,31 @@ class PlaybackCog(MusicCogBase, name="Playback"): await file.save(temporary.name) if not await MediaProbe().is_media(temporary.name): await interaction.response.send_message( - "❌ Przesłany plik nie jest prawidłowym plikiem multimedialnym.", ephemeral=True + embed=message_embed( + "Przesyłanie pliku", + "Przesłany plik nie jest prawidłowym plikiem multimedialnym.", + ), + ephemeral=True, ) return with open(temporary.name, "rb") as source: saved = self._uploads.save(filename, source, interaction.guild.id) - await interaction.response.send_message(f"✅ Dodano plik **{saved}** do biblioteki.") + await interaction.response.send_message( + embed=message_embed("Plik przesłany", f"Dodano plik **{saved}** do biblioteki.") + ) @app_commands.command(name="deletefile", description="Usuń plik z biblioteki serwera") @app_commands.describe(filename="Plik z biblioteki") @app_commands.autocomplete(filename=_uploads_autocomplete) async def delete_file(self, interaction: discord.Interaction, filename: str) -> None: if not self._uploads.delete(filename, interaction.guild.id): - await interaction.response.send_message("Nie znaleziono pliku.", ephemeral=True) + await interaction.response.send_message( + embed=message_embed("Biblioteka plików", "Nie znaleziono pliku."), ephemeral=True + ) return - await interaction.response.send_message(f"✅ Usunięto plik **{filename}**.") + await interaction.response.send_message( + embed=message_embed("Plik usunięty", f"Usunięto plik **{filename}**.") + ) async def _history_autocomplete( self, interaction: discord.Interaction, current: str @@ -119,7 +165,7 @@ class PlaybackCog(MusicCogBase, name="Playback"): embed=discord.Embed( title="📜 Historia odtwarzania", description=listing, - color=discord.Color.gold(), + color=BRAND_PURPLE, ), view=HistoryView(self, interaction.guild.id), ) @@ -140,17 +186,25 @@ class PlaybackCog(MusicCogBase, name="Playback"): await self._enqueue_and_reply(interaction, track) except Exception as error: # noqa: BLE001 logger.error("Komenda /play nie powiodła się: %s", error) - await interaction.followup.send(f"Wystąpił błąd: {error}", ephemeral=True) + await interaction.followup.send( + embed=message_embed("Odtwarzanie", f"Wystąpił błąd: {error}"), ephemeral=True + ) @app_commands.command(name="playfile", description="Odtwórz plik przesłany na serwer") @app_commands.describe(filename="Wybierz plik z listy przesłanych") @app_commands.autocomplete(filename=_uploads_autocomplete) async def playfile(self, interaction: discord.Interaction, filename: str) -> None: + await self.play_filename(interaction, filename) + + async def play_filename(self, interaction: discord.Interaction, filename: str) -> None: if not await self._ensure_voice(interaction): return await interaction.response.defer() if not self._uploads.exists(filename, interaction.guild.id): - await interaction.followup.send("Nie znaleziono pliku na serwerze.", ephemeral=True) + await interaction.followup.send( + embed=message_embed("Odtwarzanie pliku", "Nie znaleziono pliku na serwerze."), + ephemeral=True, + ) return try: track = await self._tracks.from_file( @@ -159,7 +213,10 @@ class PlaybackCog(MusicCogBase, name="Playback"): await self._enqueue_and_reply(interaction, track) except Exception as error: # noqa: BLE001 logger.error("Komenda /playfile nie powiodła się: %s", error) - await interaction.followup.send(f"Wystąpił błąd: {error}", ephemeral=True) + await interaction.followup.send( + embed=message_embed("Odtwarzanie pliku", f"Wystąpił błąd: {error}"), + ephemeral=True, + ) @app_commands.command(name="replay", description="Odtwórz ponownie utwór z historii") @app_commands.describe(track_id="Wybierz utwór z historii odtwarzania") @@ -179,14 +236,20 @@ class PlaybackCog(MusicCogBase, name="Playback"): if 0 <= position < len(entries): entry = entries[position] if entry is None: - await interaction.followup.send("Nie znaleziono utworu w historii.", ephemeral=True) + await interaction.followup.send( + embed=message_embed("Historia", "Nie znaleziono utworu w historii."), + ephemeral=True, + ) return try: if entry.is_local: filepath = entry.replay_source or "" if not self._uploads.exists(filepath, guild_id): await interaction.followup.send( - "Plik lokalny z historii już nie istnieje na serwerze.", ephemeral=True + embed=message_embed( + "Historia", "Plik lokalny z historii już nie istnieje na serwerze." + ), + ephemeral=True, ) return track = await self._tracks.from_file( @@ -197,4 +260,6 @@ class PlaybackCog(MusicCogBase, name="Playback"): await self._enqueue_and_reply(interaction, track, prefix=" z historii") except Exception as error: # noqa: BLE001 logger.error("Komenda /replay nie powiodła się: %s", error) - await interaction.followup.send(f"Wystąpił błąd: {error}", ephemeral=True) + await interaction.followup.send( + embed=message_embed("Historia", f"Wystąpił błąd: {error}"), ephemeral=True + ) diff --git a/zyrarbot/ui/bot/cogs/player_cog.py b/zyrarbot/ui/bot/cogs/player_cog.py index 38f565d..04c04a7 100644 --- a/zyrarbot/ui/bot/cogs/player_cog.py +++ b/zyrarbot/ui/bot/cogs/player_cog.py @@ -8,6 +8,7 @@ from discord import app_commands from zyrarbot.music.music_player import MusicPlayer from zyrarbot.ui.bot.cogs.common import MusicCogBase +from zyrarbot.ui.bot.discord_theme import BRAND_PURPLE, message_embed def player_embed(player: MusicPlayer, guild: discord.Guild) -> discord.Embed: @@ -22,7 +23,9 @@ def player_embed(player: MusicPlayer, guild: discord.Guild) -> discord.Embed: if current.has_web_link: description = f"[{description}]({current.display_url})" - embed = discord.Embed(title=f"🎵 Odtwarzacz — {guild.name}", description=description) + embed = discord.Embed( + title=f"🎵 Odtwarzacz — {guild.name}", description=description, color=BRAND_PURPLE + ) embed.add_field(name="Kolejka", value=f"{len(state.queue)} utworów", inline=True) embed.add_field(name="Głośność", value=f"{int(state.volume * 100)}%", inline=True) embed.add_field(name="Zapętlanie", value=state.loop_mode.label, inline=True) @@ -61,7 +64,8 @@ class PlayerView(discord.ui.View): ) except (discord.DiscordException, RuntimeError, ValueError) as error: await interaction.response.send_message( - f"❌ Nie udało się wykonać akcji: {error}", ephemeral=True + embed=message_embed("Odtwarzacz", f"Nie udało się wykonać akcji: {error}"), + ephemeral=True, ) @discord.ui.button(label="Pauza", emoji="⏯️", style=discord.ButtonStyle.primary) @@ -73,7 +77,8 @@ class PlayerView(discord.ui.View): operation = voice.resume else: await interaction.response.send_message( - "Nic nie jest obecnie odtwarzane.", ephemeral=True + embed=message_embed("Odtwarzanie", "Nic nie jest obecnie odtwarzane."), + ephemeral=True, ) return await self.action(interaction, operation) @@ -133,7 +138,8 @@ class PlayerView(discord.ui.View): count = await self.player.restore_queue(self.guild_id) if not count: await interaction.response.send_message( - "Nie ma zapisanej kolejki dla tego serwera.", ephemeral=True + embed=message_embed("Kolejka", "Nie ma zapisanej kolejki dla tego serwera."), + ephemeral=True, ) return await self.player.trigger_state_change() @@ -171,10 +177,14 @@ class VolumeModal(discord.ui.Modal, title="Ustaw głośność"): try: value = int(self.volume.value) except ValueError: - await interaction.response.send_message("Podaj liczbę od 1 do 100.", ephemeral=True) + await interaction.response.send_message( + embed=message_embed("Głośność", "Podaj liczbę od 1 do 100."), ephemeral=True + ) return if not 1 <= value <= 100: - await interaction.response.send_message("Podaj liczbę od 1 do 100.", ephemeral=True) + await interaction.response.send_message( + embed=message_embed("Głośność", "Podaj liczbę od 1 do 100."), ephemeral=True + ) return self.player_view.player.set_volume(self.player_view.guild_id, value / 100) await self.player_view.player.trigger_state_change() diff --git a/zyrarbot/ui/bot/cogs/queue_cog.py b/zyrarbot/ui/bot/cogs/queue_cog.py index 8b28727..d7bcd9e 100644 --- a/zyrarbot/ui/bot/cogs/queue_cog.py +++ b/zyrarbot/ui/bot/cogs/queue_cog.py @@ -7,6 +7,7 @@ from discord.ext import commands from zyrarbot.music.music_player import MusicPlayer from zyrarbot.settings import Settings from zyrarbot.ui.bot.cogs.common import MusicCogBase +from zyrarbot.ui.bot.discord_theme import BRAND_PURPLE, message_embed QUEUE_PREVIEW_LIMIT = 30 AUTOCOMPLETE_LIMIT = 25 @@ -36,7 +37,9 @@ class QueueCog(MusicCogBase, name="Queue"): async def show_queue(self, interaction: discord.Interaction) -> None: state = self.player.state(interaction.guild.id) if not state.queue: - await interaction.response.send_message("Kolejka jest pusta.", ephemeral=True) + await interaction.response.send_message( + embed=message_embed("Kolejka", "Kolejka jest pusta."), ephemeral=True + ) return lines: list[str] = [] if state.current and not 0 <= state.index < len(state.queue): @@ -58,7 +61,7 @@ class QueueCog(MusicCogBase, name="Queue"): embed = discord.Embed( title="🎶 Aktualna kolejka", description=preview, - color=discord.Color.blurple(), + color=BRAND_PURPLE, ) embed.set_footer(text=f"{len(state.queue)} utworów w kolejce") await interaction.response.send_message(embed=embed) @@ -78,10 +81,15 @@ class QueueCog(MusicCogBase, name="Queue"): count = await self.player.restore_queue(interaction.guild.id) if not count: await interaction.response.send_message( - "Nie ma zapisanej kolejki dla tego serwera.", ephemeral=True + embed=message_embed( + "Przywracanie kolejki", "Nie ma zapisanej kolejki dla tego serwera." + ), + ephemeral=True, ) return - await interaction.response.send_message(f"✅ Przywrócono kolejkę ({count} utworów).") + await interaction.response.send_message( + embed=message_embed("Kolejka przywrócona", f"Przywrócono kolejkę ({count} utworów).") + ) @app_commands.command(name="remove", description="Usuń wybrany utwór z kolejki") @app_commands.describe(track_id="Utwór z kolejki") @@ -93,7 +101,9 @@ class QueueCog(MusicCogBase, name="Queue"): ) return await self.player.trigger_state_change() - await interaction.response.send_message("✅ Usunięto utwór z kolejki.") + await interaction.response.send_message( + embed=message_embed("Kolejka", "Usunięto utwór z kolejki.") + ) @app_commands.command(name="jump", description="Odtwórz wybrany utwór z kolejki") @app_commands.describe(track_id="Utwór z kolejki") @@ -107,7 +117,9 @@ class QueueCog(MusicCogBase, name="Queue"): return await self.player.jump(interaction.guild.id, position) await self.player.trigger_state_change() - await interaction.response.send_message("▶️ Rozpoczęto wybrany utwór.") + await interaction.response.send_message( + embed=message_embed("Kolejka", "▶️ Rozpoczęto wybrany utwór.") + ) @app_commands.command(name="move", description="Przenieś utwór przed inny utwór") @app_commands.describe(track_id="Utwór do przeniesienia", before_id="Utwór docelowy") @@ -123,7 +135,9 @@ class QueueCog(MusicCogBase, name="Queue"): ids.insert(ids.index(before_id), track_id) self.player.reorder(interaction.guild.id, ids) await self.player.trigger_state_change() - await interaction.response.send_message("✅ Zmieniono kolejność kolejki.") + await interaction.response.send_message( + embed=message_embed("Kolejka", "Zmieniono kolejność kolejki.") + ) @app_commands.command( name="trackplays", description="Zmień liczbę odtworzeń dla utworu w kolejce" @@ -136,17 +150,21 @@ class QueueCog(MusicCogBase, name="Queue"): track = self.player.state(interaction.guild.id).find(track_id) if track is None: await interaction.response.send_message( - "❌ Nie znaleziono utworu w kolejce.", ephemeral=True + embed=message_embed("Liczba odtworzeń", "Nie znaleziono utworu w kolejce."), + ephemeral=True, ) return if not self._settings.is_valid_plays(count): await interaction.response.send_message( - "❌ Liczba odtworzeń musi być z przedziału 1-999.", ephemeral=True + embed=message_embed("Liczba odtworzeń", "Wartość musi być z przedziału 1-999."), + ephemeral=True, ) return track.plays_left = count await self.player.trigger_state_change() title = discord.utils.escape_markdown(track.title or "") await interaction.response.send_message( - f"✅ Ustawiono liczbę odtworzeń dla utworu **{title}** na **{count}**." + embed=message_embed( + "Liczba odtworzeń", f"Ustawiono dla utworu **{title}** wartość **{count}**." + ) ) diff --git a/zyrarbot/ui/bot/cogs/settings_cog.py b/zyrarbot/ui/bot/cogs/settings_cog.py index 2cc1834..6e0de63 100644 --- a/zyrarbot/ui/bot/cogs/settings_cog.py +++ b/zyrarbot/ui/bot/cogs/settings_cog.py @@ -8,6 +8,7 @@ from zyrarbot.music.loop_mode import LoopMode from zyrarbot.music.music_player import MusicPlayer from zyrarbot.settings import Settings from zyrarbot.ui.bot.cogs.common import MusicCogBase +from zyrarbot.ui.bot.discord_theme import message_embed class SettingsCog(MusicCogBase, name="Settings"): @@ -23,7 +24,7 @@ class SettingsCog(MusicCogBase, name="Settings"): async def loop_command(self, interaction: discord.Interaction) -> None: mode = self.player.cycle_loop_mode(interaction.guild.id) await self.player.trigger_state_change() - await interaction.response.send_message(mode.label) + await interaction.response.send_message(embed=message_embed("Zapętlanie", mode.label)) @app_commands.command( name="loopplaylist", description="Włącz/wyłącz zapętlanie całej kolejki (tryb playlisty)" @@ -36,7 +37,9 @@ class SettingsCog(MusicCogBase, name="Settings"): description = ( "włączone (utwory po zakończeniu wracają na koniec kolejki)" if enabled else "wyłączone" ) - await interaction.response.send_message(f"🔁 Zapętlanie całej kolejki: **{description}**.") + await interaction.response.send_message( + embed=message_embed("Zapętlanie kolejki", f"🔁 {description}.") + ) @app_commands.command( name="defaultplays", @@ -46,11 +49,14 @@ class SettingsCog(MusicCogBase, name="Settings"): async def default_plays_command(self, interaction: discord.Interaction, count: int) -> None: if not self._settings.is_valid_plays(count): await interaction.response.send_message( - "❌ Liczba odtworzeń musi być z przedziału 1-999.", ephemeral=True + embed=message_embed("Liczba odtworzeń", "Wartość musi być z przedziału 1-999."), + ephemeral=True, ) return self.player.set_default_plays(interaction.guild.id, count) await self.player.trigger_state_change() await interaction.response.send_message( - f"✅ Domyślna liczba odtworzeń dla nowych utworów została ustawiona na: **{count}**." + embed=message_embed( + "Domyślna liczba odtworzeń", f"Ustawiono wartość dla nowych utworów: **{count}**." + ) ) diff --git a/zyrarbot/ui/bot/cogs/voice_cog.py b/zyrarbot/ui/bot/cogs/voice_cog.py index f8db215..8ae6691 100644 --- a/zyrarbot/ui/bot/cogs/voice_cog.py +++ b/zyrarbot/ui/bot/cogs/voice_cog.py @@ -5,6 +5,7 @@ from discord.ext import commands from zyrarbot.music.notification_channel_resolver import NotificationChannelResolver from zyrarbot.ui.bot.cogs.common import MusicCogBase +from zyrarbot.ui.bot.discord_theme import message_embed class VoiceCog(MusicCogBase, name="Voice"): @@ -42,7 +43,9 @@ class VoiceCog(MusicCogBase, name="Voice"): interaction.guild.id, NotificationChannelResolver.first_writable(interaction.guild) ) await self.player.trigger_state_change() - await interaction.response.send_message(f"✅ Połączono z kanałem **{channel.name}**.") + await interaction.response.send_message( + embed=message_embed("Kanał głosowy", f"Połączono z kanałem **{channel.name}**.") + ) @discord.app_commands.command(name="notificationchannel", description="Ustaw kanał logów") @discord.app_commands.describe(channel="Kanał tekstowy") @@ -51,22 +54,28 @@ class VoiceCog(MusicCogBase, name="Voice"): ) -> None: if channel not in NotificationChannelResolver.writable_channels(interaction.guild): await interaction.response.send_message( - "Bot nie może pisać na tym kanale.", ephemeral=True + embed=message_embed("Kanał logów", "Bot nie może pisać na tym kanale."), + ephemeral=True, ) return self.player.set_notification_channel(interaction.guild.id, channel) self.bot.config.set_notification_channel_id(interaction.guild.id, channel.id) await self.player.trigger_state_change() - await interaction.response.send_message(f"✅ Kanał logów ustawiony na {channel.mention}.") + await interaction.response.send_message( + embed=message_embed("Kanał logów", f"Ustawiono kanał: {channel.mention}.") + ) @discord.app_commands.command(name="leave", description="Odłącz bota od kanału głosowego") async def leave(self, interaction: discord.Interaction) -> None: if interaction.guild.voice_client is None: await interaction.response.send_message( - "Nie jestem na kanale głosowym.", ephemeral=True + embed=message_embed("Kanał głosowy", "Bot nie jest na kanale głosowym."), + ephemeral=True, ) return self.player.save_queue(interaction.guild.id) await interaction.guild.voice_client.disconnect() self.player.clear(interaction.guild.id) - await interaction.response.send_message("Rozłączono z kanału głosowego.") + await interaction.response.send_message( + embed=message_embed("Kanał głosowy", "Rozłączono z kanału głosowego.") + ) diff --git a/zyrarbot/ui/bot/discord_theme.py b/zyrarbot/ui/bot/discord_theme.py new file mode 100644 index 0000000..d7023a2 --- /dev/null +++ b/zyrarbot/ui/bot/discord_theme.py @@ -0,0 +1,12 @@ +from __future__ import annotations + +import discord + +BRAND_PURPLE = discord.Color.from_rgb(139, 92, 246) + + +def message_embed(title: str, description: str, *, footer: str | None = None) -> discord.Embed: + embed = discord.Embed(title=title, description=description, color=BRAND_PURPLE) + if footer: + embed.set_footer(text=footer) + return embed