212 lines
8.9 KiB
Python
212 lines
8.9 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import inspect
|
|
|
|
import discord
|
|
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:
|
|
state = player.state(guild.id)
|
|
current = state.current
|
|
if current is None:
|
|
description = "Nic nie jest obecnie odtwarzane."
|
|
else:
|
|
title = discord.utils.escape_markdown(current.title or "Nieznany tytuł")
|
|
plays = player.plays_left(current, guild.id)
|
|
description = f"▶️ **{title}**\nPozostałe odtworzenia: **{plays}**"
|
|
if current.has_web_link:
|
|
description = f"[{description}]({current.display_url})"
|
|
|
|
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)
|
|
if state.queue:
|
|
upcoming = []
|
|
for position, track in enumerate(state.queue[:10], start=1):
|
|
marker = "▶️ " if track is current else ""
|
|
upcoming.append(f"{marker}{position}. {track.title or 'Nieznany tytuł'}")
|
|
embed.add_field(name="Najbliższe utwory", value="\n".join(upcoming), inline=False)
|
|
embed.set_footer(text="Przyciski sterują odtwarzaczem na tym serwerze")
|
|
return embed
|
|
|
|
|
|
class PlayerView(discord.ui.View):
|
|
def __init__(self, player: MusicPlayer, guild: discord.Guild):
|
|
super().__init__(timeout=1800)
|
|
self.player = player
|
|
self.guild_id = guild.id
|
|
|
|
async def refresh(self, interaction: discord.Interaction) -> None:
|
|
if interaction.message:
|
|
guild = interaction.guild
|
|
if guild:
|
|
await interaction.message.edit(embed=player_embed(self.player, guild), view=self)
|
|
|
|
async def action(self, interaction: discord.Interaction, operation) -> None:
|
|
try:
|
|
result = operation()
|
|
if inspect.isawaitable(result):
|
|
await result
|
|
await self.player.trigger_state_change()
|
|
if interaction.guild is None:
|
|
return
|
|
await interaction.response.edit_message(
|
|
embed=player_embed(self.player, interaction.guild), view=self
|
|
)
|
|
except (discord.DiscordException, RuntimeError, ValueError) as error:
|
|
await interaction.response.send_message(
|
|
embed=message_embed("Odtwarzacz", f"Nie udało się wykonać akcji: {error}"),
|
|
ephemeral=True,
|
|
)
|
|
|
|
@discord.ui.button(label="Pauza", emoji="⏯️", style=discord.ButtonStyle.primary)
|
|
async def pause_resume(self, interaction: discord.Interaction, _: discord.ui.Button) -> None:
|
|
voice = self.player.voice_client(self.guild_id)
|
|
if voice and voice.is_playing():
|
|
operation = voice.pause
|
|
elif voice and voice.is_paused():
|
|
operation = voice.resume
|
|
else:
|
|
await interaction.response.send_message(
|
|
embed=message_embed("Odtwarzanie", "Nic nie jest obecnie odtwarzane."),
|
|
ephemeral=True,
|
|
)
|
|
return
|
|
await self.action(interaction, operation)
|
|
|
|
@discord.ui.button(label="Pomiń", emoji="⏭️", style=discord.ButtonStyle.secondary)
|
|
async def skip(self, interaction: discord.Interaction, _: discord.ui.Button) -> None:
|
|
if not self.player.is_active(self.guild_id):
|
|
await interaction.response.send_message(
|
|
"Nic nie jest obecnie odtwarzane.", ephemeral=True
|
|
)
|
|
return
|
|
previous = self.player.state(self.guild_id).current
|
|
self.player.skip(self.guild_id)
|
|
for _ in range(20):
|
|
await asyncio.sleep(0.05)
|
|
if self.player.state(self.guild_id).current is not previous:
|
|
break
|
|
await self.player.trigger_state_change()
|
|
await interaction.response.edit_message(
|
|
embed=player_embed(self.player, interaction.guild), view=self
|
|
)
|
|
|
|
@discord.ui.button(label="Głośność", emoji="🔊", style=discord.ButtonStyle.secondary, row=1)
|
|
async def volume(self, interaction: discord.Interaction, _: discord.ui.Button) -> None:
|
|
await interaction.response.send_modal(VolumeModal(self))
|
|
|
|
@discord.ui.button(label="Stop", emoji="⏹️", style=discord.ButtonStyle.danger)
|
|
async def stop(self, interaction: discord.Interaction, _: discord.ui.Button) -> None:
|
|
async def operation() -> None:
|
|
self.player.save_queue(self.guild_id)
|
|
voice = self.player.voice_client(self.guild_id)
|
|
if voice and self.player.is_active(self.guild_id):
|
|
voice.stop()
|
|
self.player.clear(self.guild_id)
|
|
|
|
await self.action(interaction, operation)
|
|
|
|
@discord.ui.button(label="Pętla", emoji="🔁", style=discord.ButtonStyle.secondary)
|
|
async def loop(self, interaction: discord.Interaction, _: discord.ui.Button) -> None:
|
|
await self.action(
|
|
interaction,
|
|
lambda: self.player.set_loop_mode(
|
|
self.guild_id, self.player.cycle_loop_mode(self.guild_id)
|
|
),
|
|
)
|
|
|
|
@discord.ui.button(label="Odśwież", emoji="🔄", style=discord.ButtonStyle.secondary)
|
|
async def refresh_button(self, interaction: discord.Interaction, _: discord.ui.Button) -> None:
|
|
await interaction.response.edit_message(
|
|
embed=player_embed(self.player, interaction.guild), view=self
|
|
)
|
|
|
|
@discord.ui.button(
|
|
label="Przywróć kolejkę", emoji="↩️", style=discord.ButtonStyle.success, row=1
|
|
)
|
|
async def restore(self, interaction: discord.Interaction, _: discord.ui.Button) -> None:
|
|
try:
|
|
count = await self.player.restore_queue(self.guild_id)
|
|
except ValueError as error:
|
|
await interaction.response.send_message(
|
|
embed=message_embed("Kolejka", str(error)), ephemeral=True
|
|
)
|
|
return
|
|
if not count:
|
|
await interaction.response.send_message(
|
|
embed=message_embed("Kolejka", "Nie ma zapisanej kolejki dla tego serwera."),
|
|
ephemeral=True,
|
|
)
|
|
return
|
|
await self.player.trigger_state_change()
|
|
await interaction.response.edit_message(
|
|
embed=player_embed(self.player, interaction.guild), view=self
|
|
)
|
|
|
|
@discord.ui.button(label="Rozłącz", emoji="🔌", style=discord.ButtonStyle.danger, row=1)
|
|
async def disconnect(self, interaction: discord.Interaction, _: discord.ui.Button) -> None:
|
|
voice = self.player.voice_client(self.guild_id)
|
|
if voice is None:
|
|
await interaction.response.send_message(
|
|
"Bot nie jest połączony z kanałem głosowym.", ephemeral=True
|
|
)
|
|
return
|
|
self.player.save_queue(self.guild_id)
|
|
await voice.disconnect()
|
|
self.player.clear(self.guild_id)
|
|
await self.player.trigger_state_change()
|
|
await interaction.response.edit_message(
|
|
embed=player_embed(self.player, interaction.guild), view=self
|
|
)
|
|
|
|
|
|
class VolumeModal(discord.ui.Modal, title="Ustaw głośność"):
|
|
volume = discord.ui.TextInput(
|
|
label="Głośność (1-100)", placeholder="np. 75", min_length=1, max_length=3
|
|
)
|
|
|
|
def __init__(self, view: PlayerView):
|
|
super().__init__()
|
|
self.player_view = view
|
|
|
|
async def on_submit(self, interaction: discord.Interaction) -> None:
|
|
try:
|
|
value = int(self.volume.value)
|
|
except ValueError:
|
|
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(
|
|
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()
|
|
await interaction.response.edit_message(
|
|
embed=player_embed(self.player_view.player, interaction.guild),
|
|
view=self.player_view,
|
|
)
|
|
|
|
|
|
class PlayerCog(MusicCogBase, name="Player"):
|
|
"""Publiczny panel sterowania odtwarzaczem na Discordzie."""
|
|
|
|
@app_commands.command(name="player", description="Otwórz panel sterowania odtwarzaczem")
|
|
async def player_command(self, interaction: discord.Interaction) -> None:
|
|
await interaction.response.send_message(
|
|
embed=player_embed(self.player, interaction.guild),
|
|
view=PlayerView(self.player, interaction.guild),
|
|
)
|