feat: add interactive Discord queue and history actions
This commit is contained in:
3 files changed
+99
-23
No files matched your search
@@ -1,11 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
|
||||
from zyrarbot.audio.media_probe import MediaProbe
|
||||
from zyrarbot.audio.track_factory import TrackFactory
|
||||
from zyrarbot.music.music_player import MusicPlayer
|
||||
from zyrarbot.storage.uploads_repository import UploadsRepository
|
||||
@@ -13,6 +16,29 @@ from zyrarbot.ui.bot.cogs.common import MusicCogBase
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
AUTOCOMPLETE_LIMIT = 25
|
||||
HISTORY_PREVIEW_LIMIT = 25
|
||||
|
||||
|
||||
class HistoryView(discord.ui.View):
|
||||
def __init__(self, cog: PlaybackCog, guild_id: int):
|
||||
super().__init__(timeout=900)
|
||||
self.cog = cog
|
||||
entries = cog.player.history(guild_id)[:HISTORY_PREVIEW_LIMIT]
|
||||
options = [
|
||||
discord.SelectOption(label=entry.title[:100], value=entry.id) for entry in entries
|
||||
]
|
||||
if options:
|
||||
self.add_item(HistorySelect(options))
|
||||
|
||||
|
||||
class HistorySelect(discord.ui.Select):
|
||||
def __init__(self, options: list[discord.SelectOption]):
|
||||
super().__init__(placeholder="Wybierz utwór do ponownego odtworzenia", options=options)
|
||||
|
||||
async def callback(self, interaction: discord.Interaction) -> None:
|
||||
view = self.view
|
||||
if isinstance(view, HistoryView):
|
||||
await view.cog.replay_entry(interaction, self.values[0])
|
||||
|
||||
|
||||
class PlaybackCog(MusicCogBase, name="Playback"):
|
||||
@@ -37,6 +63,36 @@ class PlaybackCog(MusicCogBase, name="Playback"):
|
||||
)
|
||||
return [app_commands.Choice(name=name, value=name) for name in names]
|
||||
|
||||
@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
|
||||
)
|
||||
return
|
||||
suffix = os.path.splitext(filename)[1]
|
||||
with tempfile.NamedTemporaryFile(suffix=suffix) as temporary:
|
||||
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
|
||||
)
|
||||
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.")
|
||||
|
||||
@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)
|
||||
return
|
||||
await interaction.response.send_message(f"✅ Usunięto plik **{filename}**.")
|
||||
|
||||
async def _history_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> list[app_commands.Choice[str]]:
|
||||
@@ -47,6 +103,27 @@ class PlaybackCog(MusicCogBase, name="Playback"):
|
||||
for entry in matches[:AUTOCOMPLETE_LIMIT]
|
||||
]
|
||||
|
||||
@app_commands.command(name="history", description="Pokaż historię z wyborem utworu do replayu")
|
||||
async def history(self, interaction: discord.Interaction) -> None:
|
||||
entries = self.player.history(interaction.guild.id)
|
||||
if not entries:
|
||||
await interaction.response.send_message(
|
||||
"Historia odtwarzania jest pusta.", ephemeral=True
|
||||
)
|
||||
return
|
||||
listing = "\n".join(
|
||||
f"{position}. {discord.utils.escape_markdown(entry.title)}"
|
||||
for position, entry in enumerate(entries[:HISTORY_PREVIEW_LIMIT], start=1)
|
||||
)
|
||||
await interaction.response.send_message(
|
||||
embed=discord.Embed(
|
||||
title="📜 Historia odtwarzania",
|
||||
description=listing,
|
||||
color=discord.Color.gold(),
|
||||
),
|
||||
view=HistoryView(self, interaction.guild.id),
|
||||
)
|
||||
|
||||
@app_commands.command(name="play", description="Odtwórz utwór z YouTube")
|
||||
@app_commands.describe(query="Link YouTube lub zapytanie wyszukiwania")
|
||||
async def play(self, interaction: discord.Interaction, query: str) -> None:
|
||||
@@ -88,6 +165,9 @@ class PlaybackCog(MusicCogBase, name="Playback"):
|
||||
@app_commands.describe(track_id="Wybierz utwór z historii odtwarzania")
|
||||
@app_commands.autocomplete(track_id=_history_autocomplete)
|
||||
async def replay(self, interaction: discord.Interaction, track_id: str) -> None:
|
||||
await self.replay_entry(interaction, track_id)
|
||||
|
||||
async def replay_entry(self, interaction: discord.Interaction, track_id: str) -> None:
|
||||
if not await self._ensure_voice(interaction):
|
||||
return
|
||||
await interaction.response.defer()
|
||||
|
||||
@@ -9,7 +9,6 @@ from zyrarbot.settings import Settings
|
||||
from zyrarbot.ui.bot.cogs.common import MusicCogBase
|
||||
|
||||
QUEUE_PREVIEW_LIMIT = 30
|
||||
HISTORY_PREVIEW_LIMIT = 15
|
||||
AUTOCOMPLETE_LIMIT = 25
|
||||
|
||||
|
||||
@@ -74,28 +73,6 @@ class QueueCog(MusicCogBase, name="Queue"):
|
||||
for entry in matches[:AUTOCOMPLETE_LIMIT]
|
||||
]
|
||||
|
||||
@app_commands.command(
|
||||
name="history", description="Pokaż historię ostatnio odtwarzanych utworów"
|
||||
)
|
||||
async def show_history(self, interaction: discord.Interaction) -> None:
|
||||
entries = self.player.history(interaction.guild.id)
|
||||
if not entries:
|
||||
await interaction.response.send_message(
|
||||
"Historia odtwarzania jest pusta.", ephemeral=True
|
||||
)
|
||||
return
|
||||
listing = "\n".join(
|
||||
f"{position + 1}. {discord.utils.escape_markdown(entry.title)}"
|
||||
for position, entry in enumerate(entries[:HISTORY_PREVIEW_LIMIT])
|
||||
)
|
||||
embed = discord.Embed(
|
||||
title="📜 Historia odtwarzania",
|
||||
description=listing,
|
||||
color=discord.Color.gold(),
|
||||
)
|
||||
embed.set_footer(text=f"Ostatnie {min(len(entries), HISTORY_PREVIEW_LIMIT)} utworów")
|
||||
await interaction.response.send_message(embed=embed)
|
||||
|
||||
@app_commands.command(name="restorequeue", description="Przywróć ostatnią zapisaną kolejkę")
|
||||
async def restore_queue(self, interaction: discord.Interaction) -> None:
|
||||
count = await self.player.restore_queue(interaction.guild.id)
|
||||
@@ -132,6 +109,22 @@ class QueueCog(MusicCogBase, name="Queue"):
|
||||
await self.player.trigger_state_change()
|
||||
await interaction.response.send_message("▶️ 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")
|
||||
@app_commands.autocomplete(track_id=_queue_autocomplete, before_id=_queue_autocomplete)
|
||||
async def move(self, interaction: discord.Interaction, track_id: str, before_id: str) -> None:
|
||||
state = self.player.state(interaction.guild.id)
|
||||
if track_id == before_id or not state.find(track_id) or not state.find(before_id):
|
||||
await interaction.response.send_message(
|
||||
"❌ Wybierz dwa różne utwory z kolejki.", ephemeral=True
|
||||
)
|
||||
return
|
||||
ids = [track.id for track in state.queue if track.id != track_id]
|
||||
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.")
|
||||
|
||||
@app_commands.command(
|
||||
name="trackplays", description="Zmień liczbę odtworzeń dla utworu w kolejce"
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user