feat: przebudowa systemu kolejki od podstaw z użyciem wskaźnika indeksu (nieusuwalna kolejka z możliwością przeskoku i przewijania)
This commit is contained in:
4 files changed
+278
-185
No files matched your search
@@ -23,6 +23,8 @@ class MusicBot(commands.Cog):
|
||||
self.loop_playlists = {} # guild_id -> bool
|
||||
self.last_channels = {} # guild_id -> text channel
|
||||
self.history = {} # guild_id -> list of dicts
|
||||
self.queue_indices = {} # guild_id -> int
|
||||
self.jump_flag = {} # guild_id -> bool
|
||||
self.on_state_change_callback = None
|
||||
|
||||
def load_history(self, guild_id: int):
|
||||
@@ -111,6 +113,67 @@ class MusicBot(commands.Cog):
|
||||
def set_loop_playlist(self, guild_id: int, loop: bool):
|
||||
self.loop_playlists[guild_id] = loop
|
||||
|
||||
def get_queue_index(self, guild_id: int):
|
||||
return self.queue_indices.get(guild_id, -1)
|
||||
|
||||
def set_queue_index(self, guild_id: int, index: int):
|
||||
self.queue_indices[guild_id] = index
|
||||
|
||||
def clear_queue(self, guild_id: int):
|
||||
self.get_queue(guild_id).clear()
|
||||
self.set_current(guild_id, None)
|
||||
self.set_queue_index(guild_id, -1)
|
||||
|
||||
async def get_fresh_player(self, track):
|
||||
if track.data.get('is_local'):
|
||||
return await YTDLSource.from_file(track.data['filepath'], loop=self.bot.loop, title=track.title)
|
||||
else:
|
||||
url = track.data.get('webpage_url') or track.url
|
||||
return await YTDLSource.from_url(url, loop=self.bot.loop, stream=True)
|
||||
|
||||
async def enqueue_track(self, guild_id: int, player, channel=None, from_web=False):
|
||||
if channel:
|
||||
self.last_channels[guild_id] = channel
|
||||
|
||||
queue = self.get_queue(guild_id)
|
||||
queue.append(player)
|
||||
|
||||
voice_client = discord.utils.get(self.bot.voice_clients, guild__id=guild_id)
|
||||
is_playing = voice_client and (voice_client.is_playing() or voice_client.is_paused())
|
||||
|
||||
url = player.data.get('webpage_url') or player.url
|
||||
title_clean = discord.utils.escape_markdown(player.title or "Nieznany tytuł")
|
||||
is_http = url and str(url).startswith("http")
|
||||
|
||||
if is_playing:
|
||||
embed = discord.Embed(
|
||||
title=f"🎵 {title_clean}" if not player.data.get('is_local') else f"📁 {title_clean}",
|
||||
url=url if is_http else None,
|
||||
description=f"[Kliknij tutaj, aby otworzyć link]({url})" if is_http else "Plik lokalny / brak linku",
|
||||
color=discord.Color.blue()
|
||||
)
|
||||
if from_web:
|
||||
embed.set_author(name="Dodano do kolejki z panelu Web", icon_url="https://cdn-icons-png.flaticon.com/512/3844/3844724.png")
|
||||
else:
|
||||
embed.set_author(name="Dodano do kolejki", icon_url="https://cdn-icons-png.flaticon.com/512/3844/3844724.png")
|
||||
if player.duration:
|
||||
mins, secs = divmod(int(player.duration), 60)
|
||||
embed.add_field(name="Czas trwania", value=f"{mins}:{secs:02d}", inline=True)
|
||||
embed.set_footer(text=f"Pozycja w kolejce: {len(queue)}")
|
||||
await self.send_to_last_channel(guild_id, embed=embed)
|
||||
await self.trigger_state_change()
|
||||
return {"status": "queued", "title": player.title}
|
||||
else:
|
||||
await self.play_next(guild_id, advance=False)
|
||||
return {"status": "playing", "title": player.title}
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_voice_state_update(self, member, before, after):
|
||||
if member.id == self.bot.user.id and before.channel is not None and after.channel is None:
|
||||
guild_id = before.channel.guild.id
|
||||
self.clear_queue(guild_id)
|
||||
await self.trigger_state_change()
|
||||
|
||||
async def trigger_state_change(self):
|
||||
if self.on_state_change_callback:
|
||||
try:
|
||||
@@ -211,24 +274,13 @@ class MusicBot(commands.Cog):
|
||||
else:
|
||||
player = await YTDLSource.from_url(query, loop=self.bot.loop, stream=True)
|
||||
|
||||
if interaction.guild.voice_client.is_playing():
|
||||
self.get_queue(guild_id).append(player)
|
||||
await self.trigger_state_change()
|
||||
res = await self.enqueue_track(guild_id, player, channel=interaction.channel, from_web=False)
|
||||
if res["status"] == "queued":
|
||||
await interaction.followup.send(
|
||||
f"Dodano do kolejki: {discord.utils.escape_markdown(player.title)}",
|
||||
ephemeral=False
|
||||
)
|
||||
else:
|
||||
self.set_current(guild_id, player)
|
||||
self.add_to_history(guild_id, player)
|
||||
await self.trigger_state_change()
|
||||
interaction.guild.voice_client.play(
|
||||
player,
|
||||
after=lambda e: asyncio.run_coroutine_threadsafe(
|
||||
self.play_next(guild_id),
|
||||
self.bot.loop
|
||||
)
|
||||
)
|
||||
await interaction.followup.send(
|
||||
f"Teraz odtwarzane: {discord.utils.escape_markdown(player.title)}",
|
||||
ephemeral=False
|
||||
@@ -271,24 +323,13 @@ class MusicBot(commands.Cog):
|
||||
try:
|
||||
player = await YTDLSource.from_file(filepath, loop=self.bot.loop)
|
||||
|
||||
if interaction.guild.voice_client.is_playing():
|
||||
self.get_queue(guild_id).append(player)
|
||||
await self.trigger_state_change()
|
||||
res = await self.enqueue_track(guild_id, player, channel=interaction.channel, from_web=False)
|
||||
if res["status"] == "queued":
|
||||
await interaction.followup.send(
|
||||
f"Dodano do kolejki: {discord.utils.escape_markdown(player.title)}",
|
||||
ephemeral=False
|
||||
)
|
||||
else:
|
||||
self.set_current(guild_id, player)
|
||||
self.add_to_history(guild_id, player)
|
||||
await self.trigger_state_change()
|
||||
interaction.guild.voice_client.play(
|
||||
player,
|
||||
after=lambda e: asyncio.run_coroutine_threadsafe(
|
||||
self.play_next(guild_id),
|
||||
self.bot.loop
|
||||
)
|
||||
)
|
||||
await interaction.followup.send(
|
||||
f"Teraz odtwarzane: {discord.utils.escape_markdown(player.title)}",
|
||||
ephemeral=False
|
||||
@@ -299,65 +340,88 @@ class MusicBot(commands.Cog):
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
async def play_next(self, guild_id: int):
|
||||
async def play_next(self, guild_id: int, advance: bool = True):
|
||||
voice_client = discord.utils.get(self.bot.voice_clients, guild__id=guild_id)
|
||||
if not voice_client:
|
||||
self.set_current(guild_id, None)
|
||||
return
|
||||
|
||||
loop_current = self.get_loop_current(guild_id)
|
||||
current = self.get_current(guild_id)
|
||||
|
||||
if loop_current and current:
|
||||
try:
|
||||
if current.data.get('is_local'):
|
||||
player = await YTDLSource.from_file(current.data['filepath'], loop=self.bot.loop, title=current.title)
|
||||
else:
|
||||
url = current.data.get('webpage_url') or current.url
|
||||
player = await YTDLSource.from_url(url, loop=self.bot.loop, stream=True)
|
||||
self.set_current(guild_id, player)
|
||||
await self.trigger_state_change()
|
||||
voice_client.play(
|
||||
player,
|
||||
after=lambda e: asyncio.run_coroutine_threadsafe(
|
||||
self.play_next(guild_id),
|
||||
self.bot.loop
|
||||
)
|
||||
)
|
||||
await self.send_to_last_channel(guild_id, f"Teraz odtwarzane (zapętlone): {discord.utils.escape_markdown(player.title)}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"Error repeating song: {e}")
|
||||
elif self.get_loop_playlist(guild_id) and current:
|
||||
try:
|
||||
if current.data.get('is_local'):
|
||||
new_player = await YTDLSource.from_file(current.data['filepath'], loop=self.bot.loop, title=current.title)
|
||||
else:
|
||||
url = current.data.get('webpage_url') or current.url
|
||||
new_player = await YTDLSource.from_url(url, loop=self.bot.loop, stream=True)
|
||||
self.get_queue(guild_id).append(new_player)
|
||||
await self.trigger_state_change()
|
||||
except Exception as e:
|
||||
print(f"Error re-adding track to playlist loop: {e}")
|
||||
if self.jump_flag.get(guild_id, False):
|
||||
self.jump_flag[guild_id] = False
|
||||
advance = False
|
||||
|
||||
queue = self.get_queue(guild_id)
|
||||
if len(queue) > 0:
|
||||
next_player = queue.pop(0)
|
||||
self.set_current(guild_id, next_player)
|
||||
self.add_to_history(guild_id, next_player)
|
||||
await self.trigger_state_change()
|
||||
voice_client.play(
|
||||
next_player,
|
||||
after=lambda e: asyncio.run_coroutine_threadsafe(
|
||||
self.play_next(guild_id),
|
||||
self.bot.loop
|
||||
)
|
||||
)
|
||||
await self.send_to_last_channel(guild_id, f"Teraz odtwarzane: {discord.utils.escape_markdown(next_player.title)}")
|
||||
if len(queue) == 0:
|
||||
self.set_queue_index(guild_id, -1)
|
||||
self.set_current(guild_id, None)
|
||||
return
|
||||
|
||||
idx = self.get_queue_index(guild_id)
|
||||
if idx == -1 and len(queue) > 0:
|
||||
idx = 0
|
||||
|
||||
if not advance:
|
||||
if 0 <= idx < len(queue) and self.get_current(guild_id) is not None:
|
||||
next_idx = idx
|
||||
else:
|
||||
next_idx = len(queue) - 1
|
||||
else:
|
||||
if self.get_loop_current(guild_id):
|
||||
next_idx = idx
|
||||
elif self.get_loop_playlist(guild_id):
|
||||
next_idx = (idx + 1) % len(queue)
|
||||
else:
|
||||
next_idx = idx + 1
|
||||
|
||||
if next_idx >= len(queue):
|
||||
self.set_queue_index(guild_id, next_idx)
|
||||
self.set_current(guild_id, None)
|
||||
await self.trigger_state_change()
|
||||
await self.send_to_last_channel(guild_id, "Kolejka jest pusta.")
|
||||
await self.send_to_last_channel(guild_id, "Kolejka zakończona.")
|
||||
return
|
||||
|
||||
self.set_queue_index(guild_id, next_idx)
|
||||
track = queue[next_idx]
|
||||
|
||||
if getattr(track, 'played', False):
|
||||
try:
|
||||
player = await self.get_fresh_player(track)
|
||||
except Exception as e:
|
||||
print(f"Error getting fresh player for track {track.title}: {e}")
|
||||
await self.send_to_last_channel(guild_id, f"⚠️ Nie udało się odtworzyć: {track.title} ({e}). Pomijam.")
|
||||
return await self.play_next(guild_id, advance=True)
|
||||
else:
|
||||
player = track
|
||||
|
||||
player.played = True
|
||||
queue[next_idx] = player
|
||||
self.set_current(guild_id, player)
|
||||
self.add_to_history(guild_id, player)
|
||||
await self.trigger_state_change()
|
||||
|
||||
voice_client.play(
|
||||
player,
|
||||
after=lambda e: asyncio.run_coroutine_threadsafe(
|
||||
self.play_next(guild_id, advance=True),
|
||||
self.bot.loop
|
||||
)
|
||||
)
|
||||
|
||||
url = player.data.get('webpage_url') or player.url
|
||||
title_clean = discord.utils.escape_markdown(player.title or "Nieznany tytuł")
|
||||
is_http = url and str(url).startswith("http")
|
||||
|
||||
embed = discord.Embed(
|
||||
title=f"▶️ {title_clean}" if not player.data.get('is_local') else f"📁 {title_clean}",
|
||||
url=url if is_http else None,
|
||||
description=f"[Kliknij tutaj, aby otworzyć link]({url})" if is_http else "Plik lokalny / brak linku",
|
||||
color=discord.Color.green()
|
||||
)
|
||||
if player.duration:
|
||||
mins, secs = divmod(int(player.duration), 60)
|
||||
embed.add_field(name="Czas trwania", value=f"{mins}:{secs:02d}", inline=True)
|
||||
embed.set_footer(text=f"Pozycja w kolejce: {next_idx + 1} / {len(queue)}")
|
||||
await self.send_to_last_channel(guild_id, embed=embed)
|
||||
|
||||
async def send_to_last_channel(self, guild_id: int, text: str = None, embed: discord.Embed = None):
|
||||
channel = self.get_notification_channel(guild_id)
|
||||
@@ -379,7 +443,15 @@ class MusicBot(commands.Cog):
|
||||
)
|
||||
return
|
||||
|
||||
queue_list = "\n".join([f"{i + 1}. {discord.utils.escape_markdown(item.title)}" for i, item in enumerate(queue)])
|
||||
lines = []
|
||||
current_idx = self.get_queue_index(guild_id)
|
||||
for i, item in enumerate(queue):
|
||||
prefix = "▶️ **" if i == current_idx else f"{i + 1}. "
|
||||
suffix = "** (obecnie odtwarzane)" if i == current_idx else ""
|
||||
lines.append(f"{prefix}{discord.utils.escape_markdown(item.title)}{suffix}")
|
||||
queue_list = "\n".join(lines[:30])
|
||||
if len(lines) > 30:
|
||||
queue_list += f"\n... i {len(lines) - 30} więcej."
|
||||
await interaction.response.send_message(
|
||||
f"Aktualna kolejka:\n{queue_list}",
|
||||
ephemeral=False
|
||||
@@ -448,24 +520,13 @@ class MusicBot(commands.Cog):
|
||||
else:
|
||||
player = await YTDLSource.from_url(entry["url"], loop=self.bot.loop, stream=True)
|
||||
|
||||
if interaction.guild.voice_client.is_playing():
|
||||
self.get_queue(guild_id).append(player)
|
||||
await self.trigger_state_change()
|
||||
res = await self.enqueue_track(guild_id, player, channel=interaction.channel, from_web=False)
|
||||
if res["status"] == "queued":
|
||||
await interaction.followup.send(
|
||||
f"Dodano do kolejki z historii: {discord.utils.escape_markdown(player.title)}",
|
||||
ephemeral=False
|
||||
)
|
||||
else:
|
||||
self.set_current(guild_id, player)
|
||||
self.add_to_history(guild_id, player)
|
||||
await self.trigger_state_change()
|
||||
interaction.guild.voice_client.play(
|
||||
player,
|
||||
after=lambda e: asyncio.run_coroutine_threadsafe(
|
||||
self.play_next(guild_id),
|
||||
self.bot.loop
|
||||
)
|
||||
)
|
||||
await interaction.followup.send(
|
||||
f"Teraz odtwarzane z historii: {discord.utils.escape_markdown(player.title)}",
|
||||
ephemeral=False
|
||||
@@ -530,8 +591,7 @@ class MusicBot(commands.Cog):
|
||||
guild_id = interaction.guild.id
|
||||
if interaction.guild.voice_client.is_playing():
|
||||
interaction.guild.voice_client.stop()
|
||||
self.get_queue(guild_id).clear()
|
||||
self.set_current(guild_id, None)
|
||||
self.clear_queue(guild_id)
|
||||
await self.trigger_state_change()
|
||||
await interaction.response.send_message(
|
||||
"Zatrzymano odtwarzanie i wyczyszczono kolejkę.",
|
||||
@@ -587,8 +647,7 @@ class MusicBot(commands.Cog):
|
||||
guild_id = interaction.guild.id
|
||||
if interaction.guild.voice_client is not None:
|
||||
await interaction.guild.voice_client.disconnect()
|
||||
self.get_queue(guild_id).clear()
|
||||
self.set_current(guild_id, None)
|
||||
self.clear_queue(guild_id)
|
||||
await interaction.response.send_message(
|
||||
"Rozłączono z kanału głosowego.",
|
||||
ephemeral=False
|
||||
|
||||
+32
-4
@@ -409,7 +409,7 @@ function updateUI(status) {
|
||||
|
||||
// 6. Queue render (if not currently dragging)
|
||||
if (!isDragging) {
|
||||
renderQueue(status.queue);
|
||||
renderQueue(status.queue, status.queue_index);
|
||||
renderHistory(status.history || []);
|
||||
}
|
||||
|
||||
@@ -418,7 +418,7 @@ function updateUI(status) {
|
||||
}
|
||||
|
||||
// Render Queue Items list
|
||||
function renderQueue(queue) {
|
||||
function renderQueue(queue, queueIndex = -1) {
|
||||
// Clear list but keep empty queue message
|
||||
const items = queueList.querySelectorAll('.queue-item');
|
||||
items.forEach(el => el.remove());
|
||||
@@ -434,7 +434,17 @@ function renderQueue(queue) {
|
||||
|
||||
queue.forEach((track, idx) => {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'queue-item';
|
||||
let classes = 'queue-item';
|
||||
let indexHtml = `<div class="queue-index">${idx + 1}</div>`;
|
||||
|
||||
if (idx === queueIndex) {
|
||||
classes += ' playing-item';
|
||||
indexHtml = `<div class="queue-index" title="Aktualnie odtwarzane"><i data-lucide="volume-2" style="color: var(--primary);"></i></div>`;
|
||||
} else if (idx < queueIndex) {
|
||||
classes += ' played-item';
|
||||
}
|
||||
|
||||
li.className = classes;
|
||||
li.setAttribute('draggable', 'true');
|
||||
li.setAttribute('data-id', track.id);
|
||||
li.setAttribute('data-index', idx);
|
||||
@@ -443,11 +453,14 @@ function renderQueue(queue) {
|
||||
<div class="drag-handle" title="Przeciągnij, aby zmienić kolejność">
|
||||
<i data-lucide="grip-vertical"></i>
|
||||
</div>
|
||||
<div class="queue-index">${idx + 1}</div>
|
||||
${indexHtml}
|
||||
<div class="queue-track-info">
|
||||
<div class="queue-track-title" title="${track.title}">${track.title}</div>
|
||||
</div>
|
||||
<div class="queue-item-actions">
|
||||
<button class="btn-action-icon" onclick="jumpToTrack(${idx})" title="Odtwarzaj od tej pozycji">
|
||||
<i data-lucide="play"></i>
|
||||
</button>
|
||||
<button class="btn-action-icon" onclick="moveItemUp(${idx})" title="Przesuń w górę">
|
||||
<i data-lucide="arrow-up"></i>
|
||||
</button>
|
||||
@@ -639,6 +652,21 @@ async function removeTrack(id) {
|
||||
}
|
||||
}
|
||||
|
||||
// Jump to specific track index in queue
|
||||
async function jumpToTrack(idx) {
|
||||
try {
|
||||
const res = await fetch('/api/jump', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ index: idx })
|
||||
});
|
||||
if (!res.ok) throw new Error('Jump failed');
|
||||
showToast('Zmieniono odtwarzany utwór', 'info');
|
||||
} catch (err) {
|
||||
showToast('Nie udało się zmienić utworu', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle Connection
|
||||
async function toggleConnection() {
|
||||
if (currentStatus && currentStatus.active_voice_channel) {
|
||||
|
||||
@@ -776,6 +776,23 @@ body {
|
||||
background: rgba(139, 92, 246, 0.08);
|
||||
}
|
||||
|
||||
/* Playing state */
|
||||
.queue-item.playing-item {
|
||||
background: rgba(139, 92, 246, 0.15);
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 15px var(--primary-glow);
|
||||
}
|
||||
|
||||
.queue-item.playing-item .queue-track-title {
|
||||
color: var(--primary-light);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Played state */
|
||||
.queue-item.played-item {
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
cursor: grab;
|
||||
color: var(--text-muted);
|
||||
|
||||
@@ -30,6 +30,9 @@ class RemoveRequest(BaseModel):
|
||||
class ReorderRequest(BaseModel):
|
||||
queue_ids: List[str]
|
||||
|
||||
class JumpRequest(BaseModel):
|
||||
index: int
|
||||
|
||||
class ConnectRequest(BaseModel):
|
||||
channel_id: str
|
||||
|
||||
@@ -178,6 +181,7 @@ async def get_status():
|
||||
"id": item.id
|
||||
} for item in queue
|
||||
],
|
||||
"queue_index": music_cog.get_queue_index(guild_id),
|
||||
"voice_channels": all_voice_channels,
|
||||
"active_voice_channel": active_vc,
|
||||
"text_channels": text_channels,
|
||||
@@ -243,45 +247,9 @@ async def api_play(req: PlayRequest):
|
||||
else:
|
||||
player = await YTDLSource.from_url(req.query, loop=bot.loop, stream=True)
|
||||
|
||||
url = player.data.get('webpage_url') or player.url
|
||||
title_clean = discord.utils.escape_markdown(player.title or "Nieznany tytuł")
|
||||
is_http = url and str(url).startswith("http")
|
||||
|
||||
if voice_client.is_playing() or voice_client.is_paused():
|
||||
music_cog.get_queue(guild_id).append(player)
|
||||
embed = discord.Embed(
|
||||
title=f"🎵 {title_clean}",
|
||||
url=url if is_http else None,
|
||||
description=f"[Kliknij tutaj, aby otworzyć link]({url})" if is_http else "Plik lokalny / brak linku",
|
||||
color=discord.Color.blue()
|
||||
)
|
||||
embed.set_author(name="Dodano do kolejki z panelu Web", icon_url="https://cdn-icons-png.flaticon.com/512/3844/3844724.png")
|
||||
if player.duration:
|
||||
mins, secs = divmod(int(player.duration), 60)
|
||||
embed.add_field(name="Czas trwania", value=f"{mins}:{secs:02d}", inline=True)
|
||||
await music_cog.send_to_last_channel(guild_id, embed=embed)
|
||||
await broadcast_status()
|
||||
return {"status": "queued", "title": player.title}
|
||||
else:
|
||||
music_cog.set_current(guild_id, player)
|
||||
music_cog.add_to_history(guild_id, player)
|
||||
voice_client.play(
|
||||
player,
|
||||
after=lambda e: asyncio.run_coroutine_threadsafe(
|
||||
music_cog.play_next(guild_id),
|
||||
bot.loop
|
||||
)
|
||||
)
|
||||
embed = discord.Embed(
|
||||
title=f"▶️ {title_clean}",
|
||||
url=url if is_http else None,
|
||||
description=f"[Kliknij tutaj, aby otworzyć link]({url})" if is_http else "Plik lokalny / brak linku",
|
||||
color=discord.Color.green()
|
||||
)
|
||||
embed.set_author(name="Teraz odtwarzane (z panelu Web)", icon_url="https://cdn-icons-png.flaticon.com/512/3844/3844724.png")
|
||||
await music_cog.send_to_last_channel(guild_id, embed=embed)
|
||||
await broadcast_status()
|
||||
return {"status": "playing", "title": player.title}
|
||||
res = await music_cog.enqueue_track(guild_id, player, from_web=True)
|
||||
await broadcast_status()
|
||||
return res
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@@ -328,6 +296,34 @@ async def api_skip():
|
||||
return {"status": "skipped"}
|
||||
raise HTTPException(status_code=400, detail="Bot is not playing anything on this server")
|
||||
|
||||
@app.post("/api/jump")
|
||||
async def api_jump(req: JumpRequest):
|
||||
music_cog = bot.get_cog("MusicBot")
|
||||
if not music_cog:
|
||||
raise HTTPException(status_code=500, detail="MusicBot cog not loaded")
|
||||
guild = get_active_guild()
|
||||
if not guild:
|
||||
raise HTTPException(status_code=400, detail="Bot has no active server context")
|
||||
guild_id = guild.id
|
||||
queue = music_cog.get_queue(guild_id)
|
||||
if req.index < 0 or req.index >= len(queue):
|
||||
raise HTTPException(status_code=400, detail="Invalid track index")
|
||||
|
||||
if music_cog.get_loop_current(guild_id):
|
||||
music_cog.set_loop_current(guild_id, False)
|
||||
|
||||
music_cog.set_queue_index(guild_id, req.index)
|
||||
music_cog.jump_flag[guild_id] = True
|
||||
|
||||
voice_client = discord.utils.get(bot.voice_clients, guild__id=guild_id)
|
||||
if voice_client and (voice_client.is_playing() or voice_client.is_paused()):
|
||||
voice_client.stop()
|
||||
else:
|
||||
await music_cog.play_next(guild_id, advance=False)
|
||||
|
||||
await broadcast_status()
|
||||
return {"status": "jumped"}
|
||||
|
||||
@app.post("/api/stop")
|
||||
async def api_stop():
|
||||
music_cog = bot.get_cog("MusicBot")
|
||||
@@ -343,8 +339,7 @@ async def api_stop():
|
||||
if voice_client and (voice_client.is_playing() or voice_client.is_paused()):
|
||||
voice_client.stop()
|
||||
|
||||
music_cog.get_queue(guild_id).clear()
|
||||
music_cog.set_current(guild_id, None)
|
||||
music_cog.clear_queue(guild_id)
|
||||
await broadcast_status()
|
||||
return {"status": "stopped"}
|
||||
|
||||
@@ -443,14 +438,35 @@ async def api_remove(req: RemoveRequest):
|
||||
|
||||
guild_id = guild.id
|
||||
queue = music_cog.get_queue(guild_id)
|
||||
initial_len = len(queue)
|
||||
new_queue = [item for item in queue if item.id != req.id]
|
||||
music_cog.queues[guild_id] = new_queue
|
||||
current_idx = music_cog.get_queue_index(guild_id)
|
||||
|
||||
if len(new_queue) < initial_len:
|
||||
await broadcast_status()
|
||||
return {"status": "removed"}
|
||||
raise HTTPException(status_code=404, detail="Track not found in queue")
|
||||
removed_idx = -1
|
||||
for idx, item in enumerate(queue):
|
||||
if item.id == req.id:
|
||||
removed_idx = idx
|
||||
break
|
||||
|
||||
if removed_idx == -1:
|
||||
raise HTTPException(status_code=404, detail="Track not found in queue")
|
||||
|
||||
if removed_idx == current_idx:
|
||||
new_queue = [item for idx, item in enumerate(queue) if idx != removed_idx]
|
||||
music_cog.queues[guild_id] = new_queue
|
||||
music_cog.set_queue_index(guild_id, current_idx)
|
||||
music_cog.jump_flag[guild_id] = True
|
||||
voice_client = discord.utils.get(bot.voice_clients, guild__id=guild_id)
|
||||
if voice_client and (voice_client.is_playing() or voice_client.is_paused()):
|
||||
voice_client.stop()
|
||||
else:
|
||||
await music_cog.play_next(guild_id, advance=False)
|
||||
else:
|
||||
new_queue = [item for idx, item in enumerate(queue) if idx != removed_idx]
|
||||
music_cog.queues[guild_id] = new_queue
|
||||
if removed_idx < current_idx:
|
||||
music_cog.set_queue_index(guild_id, current_idx - 1)
|
||||
|
||||
await broadcast_status()
|
||||
return {"status": "removed"}
|
||||
|
||||
@app.post("/api/reorder")
|
||||
async def api_reorder(req: ReorderRequest):
|
||||
@@ -464,6 +480,9 @@ async def api_reorder(req: ReorderRequest):
|
||||
|
||||
guild_id = guild.id
|
||||
queue = music_cog.get_queue(guild_id)
|
||||
current_idx = music_cog.get_queue_index(guild_id)
|
||||
curr_item = queue[current_idx] if 0 <= current_idx < len(queue) else None
|
||||
|
||||
queue_map = {item.id: item for item in queue}
|
||||
|
||||
new_queue = []
|
||||
@@ -477,6 +496,12 @@ async def api_reorder(req: ReorderRequest):
|
||||
new_queue.append(item)
|
||||
|
||||
music_cog.queues[guild_id] = new_queue
|
||||
if curr_item:
|
||||
try:
|
||||
music_cog.set_queue_index(guild_id, new_queue.index(curr_item))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
await broadcast_status()
|
||||
return {"status": "success"}
|
||||
|
||||
@@ -543,44 +568,9 @@ async def api_play_file(req: PlayFileRequest):
|
||||
|
||||
try:
|
||||
player = await YTDLSource.from_file(filepath, loop=bot.loop)
|
||||
title_clean = discord.utils.escape_markdown(player.title or "Nieznany tytuł")
|
||||
|
||||
if voice_client.is_playing() or voice_client.is_paused():
|
||||
music_cog.get_queue(guild_id).append(player)
|
||||
embed = discord.Embed(
|
||||
title=f"📁 {title_clean}",
|
||||
description="Plik lokalny z serwerowej bazy plików",
|
||||
color=discord.Color.blue()
|
||||
)
|
||||
embed.set_author(name="Dodano do kolejki z panelu Web", icon_url="https://cdn-icons-png.flaticon.com/512/3844/3844724.png")
|
||||
if player.duration:
|
||||
mins, secs = divmod(int(player.duration), 60)
|
||||
embed.add_field(name="Czas trwania", value=f"{mins}:{secs:02d}", inline=True)
|
||||
await music_cog.send_to_last_channel(guild_id, embed=embed)
|
||||
await broadcast_status()
|
||||
return {"status": "queued", "title": player.title}
|
||||
else:
|
||||
music_cog.set_current(guild_id, player)
|
||||
music_cog.add_to_history(guild_id, player)
|
||||
voice_client.play(
|
||||
player,
|
||||
after=lambda e: asyncio.run_coroutine_threadsafe(
|
||||
music_cog.play_next(guild_id),
|
||||
bot.loop
|
||||
)
|
||||
)
|
||||
embed = discord.Embed(
|
||||
title=f"▶️ {title_clean}",
|
||||
description="Plik lokalny z serwerowej bazy plików",
|
||||
color=discord.Color.green()
|
||||
)
|
||||
embed.set_author(name="Teraz odtwarzane (z panelu Web)", icon_url="https://cdn-icons-png.flaticon.com/512/3844/3844724.png")
|
||||
if player.duration:
|
||||
mins, secs = divmod(int(player.duration), 60)
|
||||
embed.add_field(name="Czas trwania", value=f"{mins}:{secs:02d}", inline=True)
|
||||
await music_cog.send_to_last_channel(guild_id, embed=embed)
|
||||
await broadcast_status()
|
||||
return {"status": "playing", "title": player.title}
|
||||
res = await music_cog.enqueue_track(guild_id, player, from_web=True)
|
||||
await broadcast_status()
|
||||
return res
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@@ -628,8 +618,7 @@ async def api_disconnect():
|
||||
|
||||
music_cog = bot.get_cog("MusicBot")
|
||||
if music_cog:
|
||||
music_cog.get_queue(guild_id).clear()
|
||||
music_cog.set_current(guild_id, None)
|
||||
music_cog.clear_queue(guild_id)
|
||||
|
||||
await broadcast_status()
|
||||
return {"status": "disconnected"}
|
||||
|
||||
Reference in new issue
Block a user