diff --git a/bot.py b/bot.py index 83b425b..1773eba 100644 --- a/bot.py +++ b/bot.py @@ -599,19 +599,27 @@ class MusicBot(commands.Cog): ephemeral=True ) - @app_commands.command(name="loop", description="Włącz/wyłącz zapętlanie aktualnego utworu") + @app_commands.command(name="loop", description="Zmień tryb zapętlania (Wyłączone -> Cała kolejka -> Jeden utwór)") async def loop_command(self, interaction: discord.Interaction): - """Włącz/wyłącz zapętlanie aktualnego utworu""" + """Zmień tryb zapętlania jak w YT Music/Spotify""" guild_id = interaction.guild.id - loop_current = not self.get_loop_current(guild_id) - self.set_loop_current(guild_id, loop_current) - if loop_current: + loop_current = self.get_loop_current(guild_id) + loop_pl = self.get_loop_playlist(guild_id) + + if not loop_current and not loop_pl: + self.set_loop_playlist(guild_id, True) + self.set_loop_current(guild_id, False) + msg = "🔁 Tryb zapętlania: **Cała kolejka (playlista)**" + elif loop_pl and not loop_current: self.set_loop_playlist(guild_id, False) - state = "włączone" if loop_current else "wyłączone" - await interaction.response.send_message( - f"Zapętlanie aktualnego utworu zostało {state}.", - ephemeral=False - ) + self.set_loop_current(guild_id, True) + msg = "🔂 Tryb zapętlania: **Jeden utwór**" + else: + self.set_loop_playlist(guild_id, False) + self.set_loop_current(guild_id, False) + msg = "➡️ Tryb zapętlania: **Wyłączone**" + + await interaction.response.send_message(msg, ephemeral=False) @app_commands.command(name="loopplaylist", description="Włącz/wyłącz zapętlanie całej kolejki (tryb playlisty)") async def loopplaylist_command(self, interaction: discord.Interaction): diff --git a/static/app.js b/static/app.js index 6b21c63..c8772b6 100644 --- a/static/app.js +++ b/static/app.js @@ -31,7 +31,6 @@ const playPauseIcon = document.getElementById('play-pause-icon'); const btnSkip = document.getElementById('btn-skip'); const btnStop = document.getElementById('btn-stop'); const btnLoop = document.getElementById('btn-loop'); -const btnLoopPlaylist = document.getElementById('btn-loop-playlist'); const vinylRecord = document.getElementById('vinyl-record'); const musicWave = document.getElementById('music-wave'); const volumeSlider = document.getElementById('volume-slider'); @@ -142,9 +141,6 @@ function bindEvents() { btnSkip.addEventListener('click', skipTrack); btnStop.addEventListener('click', stopPlayback); btnLoop.addEventListener('click', toggleLoop); - if (btnLoopPlaylist) { - btnLoopPlaylist.addEventListener('click', toggleLoopPlaylist); - } // Volume slider volumeSlider.addEventListener('input', (e) => { @@ -375,28 +371,39 @@ function updateUI(status) { updateVolumeIcon(status.volume); } - // 5. Loop State - if (status.loop_single) { - loopBadge.style.display = 'flex'; - btnLoop.classList.add('active'); - btnLoop.style.background = 'var(--primary)'; - btnLoop.style.color = 'var(--bg-card)'; - } else { - loopBadge.style.display = 'none'; - btnLoop.classList.remove('active'); - btnLoop.style.background = ''; - btnLoop.style.color = ''; - } + // 5. Loop State (Combined toggle: Off -> Playlist -> Single) + let loopMode = 'off'; + if (status.loop_single) loopMode = 'single'; + else if (status.loop_playlist) loopMode = 'playlist'; - if (btnLoopPlaylist) { - if (status.loop_playlist) { - btnLoopPlaylist.classList.add('active'); - btnLoopPlaylist.style.background = 'var(--primary)'; - btnLoopPlaylist.style.color = 'var(--bg-card)'; + if (btnLoop.dataset.loopMode !== loopMode) { + btnLoop.dataset.loopMode = loopMode; + if (loopMode === 'single') { + btnLoop.innerHTML = ''; + btnLoop.classList.add('active'); + btnLoop.style.background = 'var(--primary)'; + btnLoop.style.color = 'var(--bg-card)'; + btnLoop.title = "Zapętlenie: jeden utwór (kliknij, aby wyłączyć)"; + loopBadge.style.display = 'flex'; + loopBadge.innerHTML = ' Zapętlenie utworu'; + } else if (loopMode === 'playlist') { + btnLoop.innerHTML = ''; + btnLoop.classList.add('active'); + btnLoop.style.background = 'var(--primary)'; + btnLoop.style.color = 'var(--bg-card)'; + btnLoop.title = "Zapętlenie: cała kolejka (kliknij, aby zapętlić jeden utwór)"; + loopBadge.style.display = 'flex'; + loopBadge.innerHTML = ' Zapętlenie kolejki'; } else { - btnLoopPlaylist.classList.remove('active'); - btnLoopPlaylist.style.background = ''; - btnLoopPlaylist.style.color = ''; + btnLoop.innerHTML = ''; + btnLoop.classList.remove('active'); + btnLoop.style.background = ''; + btnLoop.style.color = ''; + btnLoop.title = "Zapętlenie: wyłączone (kliknij, aby zapętlić kolejkę)"; + loopBadge.style.display = 'none'; + } + if (window.lucide) { + lucide.createIcons(); } } @@ -700,35 +707,21 @@ async function stopPlayback() { } } -// Toggle Loop State +// Toggle Loop State (Cycle: Off -> Playlist -> Single -> Off) async function toggleLoop() { if (!currentStatus) return; - const nextState = !currentStatus.loop_single; try { - const res = await fetch('/api/loop', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ loop: nextState }) - }); - if (!res.ok) throw new Error('Loop failed'); - showToast(nextState ? 'Zapętlanie utworu włączone' : 'Zapętlanie utworu wyłączone', 'info'); + const res = await fetch('/api/loop_cycle', { method: 'POST' }); + if (!res.ok) throw new Error('Loop cycle failed'); + const data = await res.json(); + if (data.mode === 'playlist') showToast('Zapętlanie kolejki włączone', 'info'); + else if (data.mode === 'single') showToast('Zapętlanie utworu włączone', 'info'); + else showToast('Zapętlanie wyłączone', 'info'); } catch (err) { showToast('Błąd ustawiania pętli', 'error'); } } -// Toggle Playlist Loop State -async function toggleLoopPlaylist() { - try { - const res = await fetch('/api/loop_playlist', { method: 'POST' }); - const data = await res.json(); - if (!res.ok) throw new Error(data.detail || 'Błąd zapętlania'); - showToast(data.loop_playlist ? 'Włączono zapętlanie całej kolejki (playlisty)!' : 'Wyłączono zapętlanie kolejki.', 'info'); - } catch (err) { - showToast('Nie udało się zmienić trybu zapętlania', 'error'); - } -} - // Set Volume async function setVolume(vol) { try { diff --git a/static/index.html b/static/index.html index c8b78bd..2febe77 100644 --- a/static/index.html +++ b/static/index.html @@ -112,10 +112,7 @@ - - diff --git a/web.py b/web.py index 869d3d8..d75d40f 100644 --- a/web.py +++ b/web.py @@ -402,6 +402,35 @@ async def api_loop_playlist(): await broadcast_status() return {"status": "success", "loop_playlist": new_state} +@app.post("/api/loop_cycle") +async def api_loop_cycle(): + 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") + + loop_current = music_cog.get_loop_current(guild.id) + loop_pl = music_cog.get_loop_playlist(guild.id) + + if not loop_current and not loop_pl: + music_cog.set_loop_playlist(guild.id, True) + music_cog.set_loop_current(guild.id, False) + mode = "playlist" + elif loop_pl and not loop_current: + music_cog.set_loop_playlist(guild.id, False) + music_cog.set_loop_current(guild.id, True) + mode = "single" + else: + music_cog.set_loop_playlist(guild.id, False) + music_cog.set_loop_current(guild.id, False) + mode = "off" + + await broadcast_status() + return {"status": "success", "mode": mode} + @app.post("/api/remove") async def api_remove(req: RemoveRequest): music_cog = bot.get_cog("MusicBot")