feat: dodanie trybu zapętlania całej kolejki (playlisty) w Discord (/loopplaylist) i w dashboardzie web
This commit is contained in:
4 files changed
+90
No files matched your search
@@ -19,6 +19,7 @@ class MusicBot(commands.Cog):
|
|||||||
self.currents = {} # guild_id -> player
|
self.currents = {} # guild_id -> player
|
||||||
self.volumes = {} # guild_id -> volume float
|
self.volumes = {} # guild_id -> volume float
|
||||||
self.loop_currents = {} # guild_id -> bool
|
self.loop_currents = {} # guild_id -> bool
|
||||||
|
self.loop_playlists = {} # guild_id -> bool
|
||||||
self.last_channels = {} # guild_id -> text channel
|
self.last_channels = {} # guild_id -> text channel
|
||||||
self.history = {} # guild_id -> list of dicts
|
self.history = {} # guild_id -> list of dicts
|
||||||
self.on_state_change_callback = None
|
self.on_state_change_callback = None
|
||||||
@@ -94,6 +95,12 @@ class MusicBot(commands.Cog):
|
|||||||
def set_loop_current(self, guild_id: int, loop: bool):
|
def set_loop_current(self, guild_id: int, loop: bool):
|
||||||
self.loop_currents[guild_id] = loop
|
self.loop_currents[guild_id] = loop
|
||||||
|
|
||||||
|
def get_loop_playlist(self, guild_id: int):
|
||||||
|
return self.loop_playlists.get(guild_id, False)
|
||||||
|
|
||||||
|
def set_loop_playlist(self, guild_id: int, loop: bool):
|
||||||
|
self.loop_playlists[guild_id] = loop
|
||||||
|
|
||||||
async def trigger_state_change(self):
|
async def trigger_state_change(self):
|
||||||
if self.on_state_change_callback:
|
if self.on_state_change_callback:
|
||||||
try:
|
try:
|
||||||
@@ -307,6 +314,17 @@ class MusicBot(commands.Cog):
|
|||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error repeating song: {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}")
|
||||||
|
|
||||||
queue = self.get_queue(guild_id)
|
queue = self.get_queue(guild_id)
|
||||||
if len(queue) > 0:
|
if len(queue) > 0:
|
||||||
@@ -573,12 +591,28 @@ class MusicBot(commands.Cog):
|
|||||||
guild_id = interaction.guild.id
|
guild_id = interaction.guild.id
|
||||||
loop_current = not self.get_loop_current(guild_id)
|
loop_current = not self.get_loop_current(guild_id)
|
||||||
self.set_loop_current(guild_id, loop_current)
|
self.set_loop_current(guild_id, loop_current)
|
||||||
|
if loop_current:
|
||||||
|
self.set_loop_playlist(guild_id, False)
|
||||||
state = "włączone" if loop_current else "wyłączone"
|
state = "włączone" if loop_current else "wyłączone"
|
||||||
await interaction.response.send_message(
|
await interaction.response.send_message(
|
||||||
f"Zapętlanie aktualnego utworu zostało {state}.",
|
f"Zapętlanie aktualnego utworu zostało {state}.",
|
||||||
ephemeral=False
|
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):
|
||||||
|
"""Włącz/wyłącz zapętlanie całej kolejki"""
|
||||||
|
guild_id = interaction.guild.id
|
||||||
|
loop_pl = not self.get_loop_playlist(guild_id)
|
||||||
|
self.set_loop_playlist(guild_id, loop_pl)
|
||||||
|
if loop_pl:
|
||||||
|
self.set_loop_current(guild_id, False)
|
||||||
|
state = "włączone (utwory po zakończeniu wracają na koniec kolejki)" if loop_pl else "wyłączone"
|
||||||
|
await interaction.response.send_message(
|
||||||
|
f"🔁 Zapętlanie całej kolejki: **{state}**.",
|
||||||
|
ephemeral=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MyBot(commands.Bot):
|
class MyBot(commands.Bot):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const playPauseIcon = document.getElementById('play-pause-icon');
|
|||||||
const btnSkip = document.getElementById('btn-skip');
|
const btnSkip = document.getElementById('btn-skip');
|
||||||
const btnStop = document.getElementById('btn-stop');
|
const btnStop = document.getElementById('btn-stop');
|
||||||
const btnLoop = document.getElementById('btn-loop');
|
const btnLoop = document.getElementById('btn-loop');
|
||||||
|
const btnLoopPlaylist = document.getElementById('btn-loop-playlist');
|
||||||
const vinylRecord = document.getElementById('vinyl-record');
|
const vinylRecord = document.getElementById('vinyl-record');
|
||||||
const musicWave = document.getElementById('music-wave');
|
const musicWave = document.getElementById('music-wave');
|
||||||
const volumeSlider = document.getElementById('volume-slider');
|
const volumeSlider = document.getElementById('volume-slider');
|
||||||
@@ -138,6 +139,9 @@ function bindEvents() {
|
|||||||
btnSkip.addEventListener('click', skipTrack);
|
btnSkip.addEventListener('click', skipTrack);
|
||||||
btnStop.addEventListener('click', stopPlayback);
|
btnStop.addEventListener('click', stopPlayback);
|
||||||
btnLoop.addEventListener('click', toggleLoop);
|
btnLoop.addEventListener('click', toggleLoop);
|
||||||
|
if (btnLoopPlaylist) {
|
||||||
|
btnLoopPlaylist.addEventListener('click', toggleLoopPlaylist);
|
||||||
|
}
|
||||||
|
|
||||||
// Volume slider
|
// Volume slider
|
||||||
volumeSlider.addEventListener('input', (e) => {
|
volumeSlider.addEventListener('input', (e) => {
|
||||||
@@ -385,9 +389,25 @@ function updateUI(status) {
|
|||||||
if (status.loop_single) {
|
if (status.loop_single) {
|
||||||
loopBadge.style.display = 'flex';
|
loopBadge.style.display = 'flex';
|
||||||
btnLoop.classList.add('active');
|
btnLoop.classList.add('active');
|
||||||
|
btnLoop.style.background = 'var(--primary)';
|
||||||
|
btnLoop.style.color = 'var(--bg-card)';
|
||||||
} else {
|
} else {
|
||||||
loopBadge.style.display = 'none';
|
loopBadge.style.display = 'none';
|
||||||
btnLoop.classList.remove('active');
|
btnLoop.classList.remove('active');
|
||||||
|
btnLoop.style.background = '';
|
||||||
|
btnLoop.style.color = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btnLoopPlaylist) {
|
||||||
|
if (status.loop_playlist) {
|
||||||
|
btnLoopPlaylist.classList.add('active');
|
||||||
|
btnLoopPlaylist.style.background = 'var(--primary)';
|
||||||
|
btnLoopPlaylist.style.color = 'var(--bg-card)';
|
||||||
|
} else {
|
||||||
|
btnLoopPlaylist.classList.remove('active');
|
||||||
|
btnLoopPlaylist.style.background = '';
|
||||||
|
btnLoopPlaylist.style.color = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Queue render (if not currently dragging)
|
// 6. Queue render (if not currently dragging)
|
||||||
@@ -707,6 +727,18 @@ async function toggleLoop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
// Set Volume
|
||||||
async function setVolume(vol) {
|
async function setVolume(vol) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -118,6 +118,9 @@
|
|||||||
<button class="btn-icon btn-secondary" id="btn-loop" title="Zapętl ten utwór">
|
<button class="btn-icon btn-secondary" id="btn-loop" title="Zapętl ten utwór">
|
||||||
<i data-lucide="repeat-1"></i>
|
<i data-lucide="repeat-1"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn-icon btn-secondary" id="btn-loop-playlist" title="Zapętlij całą kolejkę (tryb playlisty)">
|
||||||
|
<i data-lucide="repeat"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Volume Control -->
|
<!-- Volume Control -->
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ async def get_status():
|
|||||||
"playing": playing,
|
"playing": playing,
|
||||||
"paused": paused,
|
"paused": paused,
|
||||||
"loop_single": music_cog.get_loop_current(guild_id),
|
"loop_single": music_cog.get_loop_current(guild_id),
|
||||||
|
"loop_playlist": music_cog.get_loop_playlist(guild_id),
|
||||||
"volume": int(music_cog.get_volume(guild_id) * 100),
|
"volume": int(music_cog.get_volume(guild_id) * 100),
|
||||||
"current": {
|
"current": {
|
||||||
"title": current_track.title,
|
"title": current_track.title,
|
||||||
@@ -356,9 +357,29 @@ async def api_loop(req: LoopRequest):
|
|||||||
raise HTTPException(status_code=400, detail="Bot has no active server context")
|
raise HTTPException(status_code=400, detail="Bot has no active server context")
|
||||||
|
|
||||||
music_cog.set_loop_current(guild.id, req.loop)
|
music_cog.set_loop_current(guild.id, req.loop)
|
||||||
|
if req.loop:
|
||||||
|
music_cog.set_loop_playlist(guild.id, False)
|
||||||
await broadcast_status()
|
await broadcast_status()
|
||||||
return {"status": "success", "loop": req.loop}
|
return {"status": "success", "loop": req.loop}
|
||||||
|
|
||||||
|
@app.post("/api/loop_playlist")
|
||||||
|
async def api_loop_playlist():
|
||||||
|
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")
|
||||||
|
|
||||||
|
current_state = music_cog.get_loop_playlist(guild.id)
|
||||||
|
new_state = not current_state
|
||||||
|
music_cog.set_loop_playlist(guild.id, new_state)
|
||||||
|
if new_state:
|
||||||
|
music_cog.set_loop_current(guild.id, False)
|
||||||
|
await broadcast_status()
|
||||||
|
return {"status": "success", "loop_playlist": new_state}
|
||||||
|
|
||||||
@app.post("/api/remove")
|
@app.post("/api/remove")
|
||||||
async def api_remove(req: RemoveRequest):
|
async def api_remove(req: RemoveRequest):
|
||||||
music_cog = bot.get_cog("MusicBot")
|
music_cog = bot.get_cog("MusicBot")
|
||||||
|
|||||||
Reference in new issue
Block a user