From edf317d21ec358b0fe880ef41869f48793f3522b Mon Sep 17 00:00:00 2001 From: Marcin Macierzanka Date: Sun, 5 Jul 2026 16:03:58 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20dodanie=20mechanizmu=20limitu=20odtworz?= =?UTF-8?q?e=C5=84=20(TTL)=20w=20kolejce=20oraz=20konfiguracji=20w=20UI=20?= =?UTF-8?q?i=20na=20Discordzie?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 86 ++++++++++++++++++++++++++++++++++++++++++++--- static/app.js | 81 ++++++++++++++++++++++++++++++++++++++++++-- static/index.html | 8 +++++ web.py | 51 ++++++++++++++++++++++++++-- ytdl.py | 1 + 5 files changed, 218 insertions(+), 9 deletions(-) diff --git a/bot.py b/bot.py index 6404760..867529c 100644 --- a/bot.py +++ b/bot.py @@ -25,6 +25,7 @@ class MusicBot(commands.Cog): self.history = {} # guild_id -> list of dicts self.queue_indices = {} # guild_id -> int self.jump_flag = {} # guild_id -> bool + self.default_plays = self.get_default_plays() self.on_state_change_callback = None def load_history(self, guild_id: int): @@ -126,15 +127,21 @@ class MusicBot(commands.Cog): 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) + new_player = 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) + new_player = await YTDLSource.from_url(url, loop=self.bot.loop, stream=True) + new_player.id = track.id + new_player.plays_left = getattr(track, 'plays_left', self.get_default_plays()) + return new_player async def enqueue_track(self, guild_id: int, player, channel=None, from_web=False): if channel: self.last_channels[guild_id] = channel + if getattr(player, 'plays_left', None) is None: + player.plays_left = self.get_default_plays() + queue = self.get_queue(guild_id) queue.append(player) @@ -216,6 +223,36 @@ class MusicBot(commands.Cog): except Exception as e: print(f"Error saving config: {e}") + def get_default_plays(self) -> int: + config_path = "config/config.json" + if os.path.exists(config_path): + try: + import json + with open(config_path, "r") as f: + data = json.load(f) + val = data.get("default_plays") + if val is not None: + return int(val) + except Exception as e: + print(f"Error loading default_plays: {e}") + return 5 + + def set_default_plays(self, val: int): + config_path = "config/config.json" + try: + import json + os.makedirs(os.path.dirname(config_path), exist_ok=True) + data = {} + if os.path.exists(config_path): + with open(config_path, "r") as f: + data = json.load(f) + data["default_plays"] = int(val) + with open(config_path, "w") as f: + json.dump(data, f, indent=4) + self.default_plays = int(val) + except Exception as e: + print(f"Error saving default_plays: {e}") + def get_notification_channel(self, guild_id: int): if self.last_channels.get(guild_id): return self.last_channels[guild_id] @@ -394,7 +431,17 @@ class MusicBot(commands.Cog): player = track player.played = True - queue[next_idx] = player + current_plays = getattr(player, 'plays_left', self.get_default_plays()) + player.plays_left = current_plays - 1 + + if player.plays_left <= 0: + queue.pop(next_idx) + self.set_queue_index(guild_id, next_idx - 1) + if self.get_loop_current(guild_id): + self.set_loop_current(guild_id, False) + else: + queue[next_idx] = player + self.set_current(guild_id, player) self.add_to_history(guild_id, player) await self.trigger_state_change() @@ -445,10 +492,15 @@ class MusicBot(commands.Cog): lines = [] current_idx = self.get_queue_index(guild_id) + current_playing = self.get_current(guild_id) + if current_playing and (current_idx < 0 or current_idx >= len(queue)): + plays = getattr(current_playing, 'plays_left', 0) + lines.append(f"▶️ **{discord.utils.escape_markdown(current_playing.title)}** (obecnie odtwarzane - ostatnie odtworzenie) [🔁 {plays}]\n---") 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}") + plays = getattr(item, 'plays_left', self.get_default_plays()) + lines.append(f"{prefix}{discord.utils.escape_markdown(item.title)}{suffix} [🔁 {plays}]") queue_list = "\n".join(lines[:30]) if len(lines) > 30: queue_list += f"\n... i {len(lines) - 30} więcej." @@ -694,6 +746,32 @@ class MusicBot(commands.Cog): ephemeral=False ) + @app_commands.command(name="defaultplays", description="Ustaw domyślną liczbę odtworzeń dla nowych utworów w kolejce") + @app_commands.describe(count="Domyślna liczba odtworzeń (1-999)") + async def default_plays_cmd(self, interaction: discord.Interaction, count: int): + if count < 1 or count > 999: + await interaction.response.send_message("❌ Liczba odtworzeń musi być z przedziału 1-999.", ephemeral=True) + return + self.set_default_plays(count) + await self.trigger_state_change() + await interaction.response.send_message(f"✅ Domyślna liczba odtworzeń dla nowych utworów została ustawiona na: **{count}**.") + + @app_commands.command(name="trackplays", description="Zmień liczbę odtworzeń dla utworu w kolejce") + @app_commands.describe(index="Pozycja w kolejce (od 1)", count="Nowa liczba odtworzeń (1-999)") + async def track_plays_cmd(self, interaction: discord.Interaction, index: int, count: int): + guild_id = interaction.guild.id + queue = self.get_queue(guild_id) + if index < 1 or index > len(queue): + await interaction.response.send_message("❌ Nieprawidłowy numer utworu w kolejce.", ephemeral=True) + return + if count < 1 or count > 999: + await interaction.response.send_message("❌ Liczba odtworzeń musi być z przedziału 1-999.", ephemeral=True) + return + track = queue[index - 1] + track.plays_left = count + await self.trigger_state_change() + await interaction.response.send_message(f"✅ Ustawiono liczbę odtworzeń dla utworu **{discord.utils.escape_markdown(track.title)}** na **{count}**.") + class MyBot(commands.Bot): def __init__(self): diff --git a/static/app.js b/static/app.js index e799d60..9f4975d 100644 --- a/static/app.js +++ b/static/app.js @@ -175,6 +175,30 @@ function bindEvents() { if (e.target === settingsModal) closeSettingsModal(); }); } + + const btnSaveDefaultPlays = document.getElementById('btn-save-default-plays'); + if (btnSaveDefaultPlays) { + btnSaveDefaultPlays.addEventListener('click', async () => { + const inputDefaultPlays = document.getElementById('setting-default-plays'); + if (!inputDefaultPlays) return; + const val = parseInt(inputDefaultPlays.value, 10); + if (isNaN(val) || val < 1 || val > 999) { + showToast('Wartość musi być z przedziału 1-999', 'error'); + return; + } + try { + const res = await fetch('/api/settings/default_plays', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ default_plays: val }) + }); + if (!res.ok) throw new Error('Failed to save setting'); + showToast(`Domyślna liczba odtworzeń: ${val}`, 'success'); + } catch (err) { + showToast('Błąd zapisu ustawienia', 'error'); + } + }); + } // Uploads Modal Events if (btnOpenAllUploads) btnOpenAllUploads.addEventListener('click', openUploadsModal); @@ -271,6 +295,7 @@ function updateProgressBar() { // Update UI States function updateUI(status) { + currentStatus = status; // 1. Connection Status & Available Channels if (status.active_voice_channel) { statusDot.className = 'status-indicator online'; @@ -320,6 +345,12 @@ function updateUI(status) { if (status.guilds) { renderSettingsGuilds(status.guilds); } + if (status.default_plays !== undefined) { + const inputDefaultPlays = document.getElementById('setting-default-plays'); + if (inputDefaultPlays && document.activeElement !== inputDefaultPlays) { + inputDefaultPlays.value = status.default_plays; + } + } // 2. Playback State if (status.current) { @@ -422,8 +453,8 @@ function updateUI(status) { } } - // 6. Queue render (if not currently dragging) - if (!isDragging) { + // 6. Queue render (if not currently dragging and not editing track plays input) + if (!isDragging && (!document.activeElement || !document.activeElement.classList.contains('track-plays-input'))) { renderQueue(status.queue, status.queue_index); renderHistory(status.history || []); } @@ -471,6 +502,13 @@ function renderQueue(queue, queueIndex = -1) { ${indexHtml}
${track.title}
+
+ + Pozostało: + + + +