From 67b0e0309cdc82bcde64ce787deac66f4435de15 Mon Sep 17 00:00:00 2001 From: Hitomatito Date: Sun, 14 Jun 2026 15:50:59 -0500 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20comando=20/volume,=20bot=C3=B3n=20?= =?UTF-8?q?=F0=9F=94=8A=20en=20NowPlaying,=20barra=20en=20embed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cogs/music.py | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/cogs/music.py b/cogs/music.py index d182703..8a97b9e 100644 --- a/cogs/music.py +++ b/cogs/music.py @@ -199,6 +199,38 @@ async def on_submit(self, interaction: discord.Interaction): ) +class VolumeModal(discord.ui.Modal): + def __init__(self, cog: "MusicCog", player: lavalink.DefaultPlayer): + current_pct = round(player.volume / 10) + super().__init__(title="🔊 Control de volumen", timeout=30) + self.cog = cog + self.player = player + + self.volume_input = discord.ui.TextInput( + label=f"Volumen actual: {current_pct}%", + placeholder="0 — 100", + default=str(current_pct), + min_length=1, + max_length=3, + required=True, + ) + self.add_item(self.volume_input) + + async def on_submit(self, interaction: discord.Interaction): + try: + vol = int(self.volume_input.value.strip()) + except ValueError: + return await interaction.response.send_message("❌ Ingresa un número válido (0-100).", ephemeral=True) + + if not 0 <= vol <= 100: + return await interaction.response.send_message("❌ El volumen debe estar entre 0 y 100.", ephemeral=True) + + internal_vol = vol * 10 # 0-100 → 0-1000 + await self.player.set_volume(internal_vol) + print(f"[VOLUME] guild={interaction.guild.id} volumen={vol}%") + await interaction.response.send_message(f"🔊 Volumen establecido al **{vol}%**.", ephemeral=True) + + class NowPlayingView(discord.ui.View): def __init__(self, cog: "MusicCog", player: lavalink.DefaultPlayer, interaction: discord.Interaction | None = None): super().__init__(timeout=None) @@ -246,6 +278,11 @@ async def _refresh_view(self, interaction: discord.Interaction): elif "np_sort" in cid: child.disabled = len(self.player.queue) < 2 continue + elif "np_volume" in cid: + pct = round(self.player.volume / 10) + label = f"{pct}%" + child.label = label + continue elif child.style == discord.ButtonStyle.url: track = self.player.current if track and track.uri: @@ -388,6 +425,13 @@ async def sort_queue(self, interaction: discord.Interaction, button: discord.ui. view = MoveQueueView(self.cog, self.player) await interaction.response.send_message(embed=embed, view=view, ephemeral=True) + @discord.ui.button(emoji="🔊", label="Volumen", style=discord.ButtonStyle.secondary, row=2, custom_id="np_volume") + async def volume_btn(self, interaction: discord.Interaction, button: discord.ui.Button): + if not await self._check_control(interaction): + return + modal = VolumeModal(self.cog, self.player) + await interaction.response.send_modal(modal) + async def _cleanup(self): if self.message is None: return @@ -622,6 +666,10 @@ async def _build_nowplaying_embed( percent = min((position_ms / max(track.duration, 1)) * 100, 100.0) embed.add_field(name="Avance", value=f"{percent:.1f}%", inline=True) + vol_pct = round(player.volume / 10) + vol_bar = "█" * (vol_pct // 10) + "░" * (10 - vol_pct // 10) + embed.add_field(name="Volumen", value=f"{vol_pct}% {vol_bar}", inline=True) + return embed async def _build_nowplaying_embed_auto( @@ -696,6 +744,10 @@ async def _build_nowplaying_embed_auto( percent = min((position_ms / max(track.duration, 1)) * 100, 100.0) embed.add_field(name="Avance", value=f"{percent:.1f}%", inline=True) + vol_pct = round(player.volume / 10) + vol_bar = "█" * (vol_pct // 10) + "░" * (10 - vol_pct // 10) + embed.add_field(name="Volumen", value=f"{vol_pct}% {vol_bar}", inline=True) + return embed async def _send_embed(self, interaction: discord.Interaction, embed: discord.Embed): @@ -1697,6 +1749,47 @@ async def shuffle(self, interaction: discord.Interaction): print(f"[SHUFFLE] ✗ Error: {e}") await self._send_error(interaction, f"Error: {e}") + @app_commands.command( + name="volume", description="Ajusta el volumen (0-100%)", + ) + @app_commands.describe(vol="Nivel de volumen de 0 a 100") + async def volume(self, interaction: discord.Interaction, vol: int | None = None): + """Ver o cambiar el volumen actual (0-100).""" + try: + player, error_message = self._require_control_player(interaction) + if player is None: + return await self._send_error(interaction, error_message) + + if vol is None: + current_pct = round(player.volume / 10) + return await self._send_embed( + interaction, + self._build_embed( + interaction, + "🔊 Volumen actual", + f"El volumen está al **{current_pct}%**.", + color=BOT_PRIMARY, + ), + ) + + if not 0 <= vol <= 100: + return await self._send_error(interaction, "El volumen debe estar entre 0 y 100.") + + await player.set_volume(vol * 10) + print(f"[VOLUME] guild={interaction.guild.id} volumen={vol}%") + await self._send_embed( + interaction, + self._build_embed( + interaction, + "🔊 Volumen", + f"Volumen establecido al **{vol}%**.", + color=BOT_SUCCESS, + ), + ) + except Exception as e: + print(f"[VOLUME] ✗ Error: {e}") + await self._send_error(interaction, f"Error: {e}") + @app_commands.command( name="play", description="Reproduce una canción (nombre o URL)" ) From bc9ad1d071c7e76d139957b72e0897f1ba2fc3e3 Mon Sep 17 00:00:00 2001 From: Hitomatito Date: Sun, 14 Jun 2026 15:58:26 -0500 Subject: [PATCH 2/7] =?UTF-8?q?fix:=20mapeo=20volumen=201:1=20(0-100=20=3D?= =?UTF-8?q?=20Lavalink=200-100),=20sin=20amplificaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cogs/music.py | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/cogs/music.py b/cogs/music.py index 8a97b9e..262d2ea 100644 --- a/cogs/music.py +++ b/cogs/music.py @@ -201,15 +201,15 @@ async def on_submit(self, interaction: discord.Interaction): class VolumeModal(discord.ui.Modal): def __init__(self, cog: "MusicCog", player: lavalink.DefaultPlayer): - current_pct = round(player.volume / 10) + vol_pct = player.volume super().__init__(title="🔊 Control de volumen", timeout=30) self.cog = cog self.player = player self.volume_input = discord.ui.TextInput( - label=f"Volumen actual: {current_pct}%", - placeholder="0 — 100", - default=str(current_pct), + label=f"Volumen actual: {vol_pct}%", + placeholder="0 — 100 (100 = normal, sin distorsión)", + default=str(min(vol_pct, 100)), min_length=1, max_length=3, required=True, @@ -225,8 +225,7 @@ async def on_submit(self, interaction: discord.Interaction): if not 0 <= vol <= 100: return await interaction.response.send_message("❌ El volumen debe estar entre 0 y 100.", ephemeral=True) - internal_vol = vol * 10 # 0-100 → 0-1000 - await self.player.set_volume(internal_vol) + await self.player.set_volume(vol) print(f"[VOLUME] guild={interaction.guild.id} volumen={vol}%") await interaction.response.send_message(f"🔊 Volumen establecido al **{vol}%**.", ephemeral=True) @@ -279,9 +278,7 @@ async def _refresh_view(self, interaction: discord.Interaction): child.disabled = len(self.player.queue) < 2 continue elif "np_volume" in cid: - pct = round(self.player.volume / 10) - label = f"{pct}%" - child.label = label + child.label = f"{self.player.volume}%" continue elif child.style == discord.ButtonStyle.url: track = self.player.current @@ -666,9 +663,9 @@ async def _build_nowplaying_embed( percent = min((position_ms / max(track.duration, 1)) * 100, 100.0) embed.add_field(name="Avance", value=f"{percent:.1f}%", inline=True) - vol_pct = round(player.volume / 10) - vol_bar = "█" * (vol_pct // 10) + "░" * (10 - vol_pct // 10) - embed.add_field(name="Volumen", value=f"{vol_pct}% {vol_bar}", inline=True) + vol = player.volume + vol_bar = "█" * (vol // 10) + "░" * (10 - vol // 10) + embed.add_field(name="Volumen", value=f"{vol}% {vol_bar}", inline=True) return embed @@ -744,9 +741,9 @@ async def _build_nowplaying_embed_auto( percent = min((position_ms / max(track.duration, 1)) * 100, 100.0) embed.add_field(name="Avance", value=f"{percent:.1f}%", inline=True) - vol_pct = round(player.volume / 10) - vol_bar = "█" * (vol_pct // 10) + "░" * (10 - vol_pct // 10) - embed.add_field(name="Volumen", value=f"{vol_pct}% {vol_bar}", inline=True) + vol = player.volume + vol_bar = "█" * (vol // 10) + "░" * (10 - vol // 10) + embed.add_field(name="Volumen", value=f"{vol}% {vol_bar}", inline=True) return embed @@ -1761,13 +1758,12 @@ async def volume(self, interaction: discord.Interaction, vol: int | None = None) return await self._send_error(interaction, error_message) if vol is None: - current_pct = round(player.volume / 10) return await self._send_embed( interaction, self._build_embed( interaction, "🔊 Volumen actual", - f"El volumen está al **{current_pct}%**.", + f"El volumen está al **{player.volume}%**.", color=BOT_PRIMARY, ), ) @@ -1775,7 +1771,7 @@ async def volume(self, interaction: discord.Interaction, vol: int | None = None) if not 0 <= vol <= 100: return await self._send_error(interaction, "El volumen debe estar entre 0 y 100.") - await player.set_volume(vol * 10) + await player.set_volume(vol) print(f"[VOLUME] guild={interaction.guild.id} volumen={vol}%") await self._send_embed( interaction, From 6b7be39852166dc5801628f5cab97633f9989ab4 Mon Sep 17 00:00:00 2001 From: Hitomatito Date: Sun, 14 Jun 2026 16:05:14 -0500 Subject: [PATCH 3/7] =?UTF-8?q?fix:=20volumen=20silencioso=20=E2=80=94=20s?= =?UTF-8?q?in=20mensajes=20en=20chat=20al=20cambiar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cogs/music.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/cogs/music.py b/cogs/music.py index 262d2ea..a633921 100644 --- a/cogs/music.py +++ b/cogs/music.py @@ -220,14 +220,16 @@ async def on_submit(self, interaction: discord.Interaction): try: vol = int(self.volume_input.value.strip()) except ValueError: - return await interaction.response.send_message("❌ Ingresa un número válido (0-100).", ephemeral=True) + await interaction.response.defer() + return if not 0 <= vol <= 100: - return await interaction.response.send_message("❌ El volumen debe estar entre 0 y 100.", ephemeral=True) + await interaction.response.defer() + return await self.player.set_volume(vol) print(f"[VOLUME] guild={interaction.guild.id} volumen={vol}%") - await interaction.response.send_message(f"🔊 Volumen establecido al **{vol}%**.", ephemeral=True) + await interaction.response.defer() class NowPlayingView(discord.ui.View): @@ -1773,15 +1775,7 @@ async def volume(self, interaction: discord.Interaction, vol: int | None = None) await player.set_volume(vol) print(f"[VOLUME] guild={interaction.guild.id} volumen={vol}%") - await self._send_embed( - interaction, - self._build_embed( - interaction, - "🔊 Volumen", - f"Volumen establecido al **{vol}%**.", - color=BOT_SUCCESS, - ), - ) + await interaction.response.defer() except Exception as e: print(f"[VOLUME] ✗ Error: {e}") await self._send_error(interaction, f"Error: {e}") From 42bd540f76a8a146b263fad78529873341071963 Mon Sep 17 00:00:00 2001 From: Hitomatito Date: Sun, 14 Jun 2026 16:08:11 -0500 Subject: [PATCH 4/7] feat: volumen con Select dropdown (0-100%), sin modal --- cogs/music.py | 62 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/cogs/music.py b/cogs/music.py index a633921..2f471bf 100644 --- a/cogs/music.py +++ b/cogs/music.py @@ -199,37 +199,44 @@ async def on_submit(self, interaction: discord.Interaction): ) -class VolumeModal(discord.ui.Modal): +class VolumeSelect(discord.ui.Select): def __init__(self, cog: "MusicCog", player: lavalink.DefaultPlayer): - vol_pct = player.volume - super().__init__(title="🔊 Control de volumen", timeout=30) self.cog = cog self.player = player + current = player.volume - self.volume_input = discord.ui.TextInput( - label=f"Volumen actual: {vol_pct}%", - placeholder="0 — 100 (100 = normal, sin distorsión)", - default=str(min(vol_pct, 100)), - min_length=1, - max_length=3, - required=True, - ) - self.add_item(self.volume_input) - - async def on_submit(self, interaction: discord.Interaction): - try: - vol = int(self.volume_input.value.strip()) - except ValueError: - await interaction.response.defer() - return + options = [] + for vol in range(0, 101, 10): + emoji = {0: "🔇", 10: "🔈", 20: "🔈", 30: "🔉", 40: "🔉", + 50: "🔉", 60: "🔊", 70: "🔊", 80: "🔊", 90: "🔊", 100: "🔊"}.get(vol, "🔊") + label = f"{vol}%" + desc = "Mute" if vol == 0 else "Máximo" if vol == 100 else "" + options.append( + discord.SelectOption( + label=label, value=str(vol), emoji=emoji, description=desc, default=(vol == current) + ) + ) - if not 0 <= vol <= 100: - await interaction.response.defer() - return + super().__init__(placeholder="Seleccioná un volumen...", min_values=1, max_values=1, options=options) + async def callback(self, interaction: discord.Interaction): + vol = int(self.values[0]) await self.player.set_volume(vol) print(f"[VOLUME] guild={interaction.guild.id} volumen={vol}%") - await interaction.response.defer() + + for opt in self.options: + opt.default = (opt.value == str(vol)) + embed = discord.Embed( + description=f"🔊 Volumen: **{vol}%**", + color=0x2B2D31, + ) + await interaction.response.edit_message(embed=embed, view=self.view) + + +class VolumeSelectView(discord.ui.View): + def __init__(self, cog: "MusicCog", player: lavalink.DefaultPlayer): + super().__init__(timeout=30) + self.add_item(VolumeSelect(cog, player)) class NowPlayingView(discord.ui.View): @@ -428,8 +435,13 @@ async def sort_queue(self, interaction: discord.Interaction, button: discord.ui. async def volume_btn(self, interaction: discord.Interaction, button: discord.ui.Button): if not await self._check_control(interaction): return - modal = VolumeModal(self.cog, self.player) - await interaction.response.send_modal(modal) + embed = discord.Embed( + title="🔊 Seleccioná un volumen", + description="Elegí un nivel con el menú desplegable. También podés usar `/volume 0-100`.", + color=0x2B2D31, + ) + view = VolumeSelectView(self.cog, self.player) + await interaction.response.send_message(embed=embed, view=view, ephemeral=True) async def _cleanup(self): if self.message is None: From fdb20f863041fb0123b7aa542fc6472a11f81da1 Mon Sep 17 00:00:00 2001 From: Hitomatito Date: Sun, 14 Jun 2026 16:15:53 -0500 Subject: [PATCH 5/7] =?UTF-8?q?fix:=20timeout=2060s=20y=20mensaje=20claro?= =?UTF-8?q?=20sobre=20expiraci=C3=B3n=20del=20men=C3=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cogs/music.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cogs/music.py b/cogs/music.py index 2f471bf..1736121 100644 --- a/cogs/music.py +++ b/cogs/music.py @@ -235,7 +235,7 @@ async def callback(self, interaction: discord.Interaction): class VolumeSelectView(discord.ui.View): def __init__(self, cog: "MusicCog", player: lavalink.DefaultPlayer): - super().__init__(timeout=30) + super().__init__(timeout=60) self.add_item(VolumeSelect(cog, player)) @@ -437,7 +437,12 @@ async def volume_btn(self, interaction: discord.Interaction, button: discord.ui. return embed = discord.Embed( title="🔊 Seleccioná un volumen", - description="Elegí un nivel con el menú desplegable. También podés usar `/volume 0-100`.", + description=( + "Elegí un nivel con el menú desplegable.\n" + "⏱️ Este menú expira en **30 segundos** — si se cierra, " + "solo volvé a tocar 🔊 para abrir uno nuevo.\n\n" + "También podés usar `/volume 0-100`." + ), color=0x2B2D31, ) view = VolumeSelectView(self.cog, self.player) From f06a11b55d507a4d7a29b28037fafb646d0e23e8 Mon Sep 17 00:00:00 2001 From: Hitomatito Date: Sun, 14 Jun 2026 16:21:30 -0500 Subject: [PATCH 6/7] =?UTF-8?q?fix:=20espa=C3=B1ol=20neutro=20en=20textos?= =?UTF-8?q?=20de=20volumen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cogs/music.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cogs/music.py b/cogs/music.py index 1736121..7483c72 100644 --- a/cogs/music.py +++ b/cogs/music.py @@ -217,7 +217,7 @@ def __init__(self, cog: "MusicCog", player: lavalink.DefaultPlayer): ) ) - super().__init__(placeholder="Seleccioná un volumen...", min_values=1, max_values=1, options=options) + super().__init__(placeholder="Selecciona un volumen...", min_values=1, max_values=1, options=options) async def callback(self, interaction: discord.Interaction): vol = int(self.values[0]) @@ -436,12 +436,12 @@ async def volume_btn(self, interaction: discord.Interaction, button: discord.ui. if not await self._check_control(interaction): return embed = discord.Embed( - title="🔊 Seleccioná un volumen", + title="🔊 Selecciona un volumen", description=( - "Elegí un nivel con el menú desplegable.\n" - "⏱️ Este menú expira en **30 segundos** — si se cierra, " - "solo volvé a tocar 🔊 para abrir uno nuevo.\n\n" - "También podés usar `/volume 0-100`." + "Elige un nivel con el menú desplegable.\n" + "⏱️ Este menú expira en **60 segundos** — si se cierra, " + "solo toca 🔊 para abrir uno nuevo.\n\n" + "También puedes usar `/volume 0-100`." ), color=0x2B2D31, ) From 285617aa636e2cf826a4b9a0e49f24230d3bdf55 Mon Sep 17 00:00:00 2001 From: Hitomatito Date: Sun, 14 Jun 2026 16:24:48 -0500 Subject: [PATCH 7/7] =?UTF-8?q?fix:=20label=20volumen=20inicial=20en=20cre?= =?UTF-8?q?aci=C3=B3n=20del=20view?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cogs/music.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cogs/music.py b/cogs/music.py index 7483c72..8946706 100644 --- a/cogs/music.py +++ b/cogs/music.py @@ -252,6 +252,10 @@ def __init__(self, cog: "MusicCog", player: lavalink.DefaultPlayer, interaction: style=discord.ButtonStyle.url, url=track.uri, label="Abrir", emoji="🔗", row=1, )) + for child in self.children: + if isinstance(child, discord.ui.Button) and "np_volume" in (child.custom_id or ""): + child.label = f"{player.volume}%" + break async def _check_control(self, interaction: discord.Interaction) -> bool: if not interaction.user.voice or interaction.user.voice.channel.id != self.player.channel_id: