feat: Add "My Vibe" button to menu and enhance interactions.

This commit is contained in:
Lemon4ksan
2025-01-27 23:32:08 +03:00
parent c49ff949cf
commit c353de429f
8 changed files with 332 additions and 153 deletions

View File

@@ -1,5 +1,5 @@
import logging
from typing import Literal, cast
from typing import Any, Literal, cast
import discord
from yandex_music import Track, Album, Artist, Playlist
@@ -19,11 +19,11 @@ class PlayButton(Button, VoiceExtension):
logging.debug(f"Callback triggered for type: '{type(self.item).__name__}'")
if not interaction.guild:
logging.warning("No guild found in context.")
logging.warning("No guild found in PlayButton callback")
return
if not await self.voice_check(interaction):
logging.debug("Voice check failed")
logging.debug("Voice check failed in PlayButton callback")
return
gid = interaction.guild.id
@@ -41,7 +41,7 @@ class PlayButton(Button, VoiceExtension):
elif isinstance(self.item, Album):
album = await self.item.with_tracks_async()
if not album or not album.volumes:
logging.debug("Failed to fetch album tracks")
logging.debug("Failed to fetch album tracks in PlayButton callback")
await interaction.respond("Не удалось получить треки альбома.", ephemeral=True)
return
@@ -53,7 +53,7 @@ class PlayButton(Button, VoiceExtension):
elif isinstance(self.item, Artist):
artist_tracks = await self.item.get_tracks_async()
if not artist_tracks:
logging.debug("Failed to fetch artist tracks")
logging.debug("Failed to fetch artist tracks in PlayButton callback")
await interaction.respond("Не удалось получить треки артиста.", ephemeral=True)
return
@@ -65,7 +65,7 @@ class PlayButton(Button, VoiceExtension):
elif isinstance(self.item, Playlist):
short_tracks = await self.item.fetch_tracks_async()
if not short_tracks:
logging.debug("Failed to fetch playlist tracks")
logging.debug("Failed to fetch playlist tracks in PlayButton callback")
await interaction.respond("Не удалось получить треки из плейлиста.", delete_after=15)
return
@@ -77,7 +77,7 @@ class PlayButton(Button, VoiceExtension):
elif isinstance(self.item, list):
tracks = self.item.copy()
if not tracks:
logging.debug("Empty tracks list")
logging.debug("Empty tracks list in PlayButton callback")
await interaction.respond("Не удалось получить треки.", delete_after=15)
return
@@ -89,7 +89,7 @@ class PlayButton(Button, VoiceExtension):
raise ValueError(f"Unknown item type: '{type(self.item).__name__}'")
if guild.get(f'vote_{action}') and len(channel.members) > 2 and not member.guild_permissions.manage_channels:
logging.debug(f"Starting vote for '{action}'")
logging.debug(f"Starting vote for '{action}' (from PlayButton callback)")
message = cast(discord.Interaction, await interaction.respond(vote_message, delete_after=30))
response = await message.original_response()
@@ -109,7 +109,7 @@ class PlayButton(Button, VoiceExtension):
}
)
else:
logging.debug(f"Skipping vote for '{action}'")
logging.debug(f"Skipping vote for '{action}' (from PlayButton callback)")
if guild['current_track'] is not None:
self.db.modify_track(gid, tracks, 'next', 'extend')
@@ -129,6 +129,41 @@ class PlayButton(Button, VoiceExtension):
else:
await interaction.respond(response_message, delete_after=15)
class MyVibeButton(Button, VoiceExtension):
def __init__(self, item: Track | Album | Artist | Playlist | list[Track], *args, **kwargs):
Button.__init__(self, *args, **kwargs)
VoiceExtension.__init__(self, None)
self.item = item
async def callback(self, interaction: discord.Interaction):
logging.debug(f"[VIBE] Button callback for '{type(self.item).__name__}'")
if not await self.voice_check(interaction):
return
gid = interaction.guild_id
if not gid:
logging.warning(f"[VIBE] Guild ID is None in button callback")
return
guild = self.db.get_guild(gid)
channel = cast(discord.VoiceChannel, interaction.channel)
if len(channel.members) > 2 and not guild['always_allow_menu']:
logging.info(f"[VIBE] Button callback declined: other members are present in the voice channel")
await interaction.respond("❌ Вы не единственный в голосовом канале.", ephemeral=True)
return
track_type_map: dict[Any, Literal['track', 'album', 'artist', 'playlist', 'user']] = {
Track: 'track', Album: 'album', Artist: 'artist', Playlist: 'playlist', list: 'user'
} # NOTE: Likes playlist should have its own entry instead of 'user:onyourwave'
await self.send_menu_message(interaction)
await self.update_vibe(
interaction,
track_type_map[type(self.item)],
cast(int, self.item.uid) if isinstance(self.item, Playlist) else cast(int | str, self.item.id) if not isinstance(self.item, list) else 'onyourwave'
)
class ListenView(View):
def __init__(self, item: Track | Album | Artist | Playlist | list[Track], *items: Item, timeout: float | None = 3600, disable_on_timeout: bool = False):
super().__init__(*items, timeout=timeout, disable_on_timeout=disable_on_timeout)
@@ -150,11 +185,13 @@ class ListenView(View):
self.add_item(PlayButton(item, label="Слушать в голосовом канале", style=ButtonStyle.gray))
return
self.button1: Button = Button(label="Слушать в приложении", style=ButtonStyle.gray, url=link_app)
self.button2: Button = Button(label="Слушать в браузере", style=ButtonStyle.gray, url=link_web)
self.button3: PlayButton = PlayButton(item, label="Слушать в голосовом канале", style=ButtonStyle.gray)
self.button1: Button = Button(label="Слушать в приложении", style=ButtonStyle.gray, url=link_app, row=0)
self.button2: Button = Button(label="Слушать в браузере", style=ButtonStyle.gray, url=link_web, row=0)
self.button3: PlayButton = PlayButton(item, label="Слушать в голосовом канале", style=ButtonStyle.gray, row=0)
self.button4: MyVibeButton = MyVibeButton(item, label="Моя Волна", style=ButtonStyle.gray, emoji="🌊", row=1)
if item.available:
# self.add_item(self.button1) # Discord doesn't allow well formed URLs in buttons for some reason.
self.add_item(self.button2)
self.add_item(self.button3)
self.add_item(self.button3)
self.add_item(self.button4)

View File

@@ -1,9 +1,10 @@
import logging
from typing import Self, cast
from discord.ui import View, Button, Item
from discord import VoiceChannel, ButtonStyle, Interaction, ApplicationContext, RawReactionActionEvent, Embed
from discord.ui import View, Button, Item, Modal, Select
from discord import VoiceChannel, ButtonStyle, Interaction, ApplicationContext, RawReactionActionEvent, Embed, ComponentType, SelectOption
import yandex_music.exceptions
from yandex_music import Track, ClientAsync
from MusicBot.cogs.utils.voice_extension import VoiceExtension
@@ -123,8 +124,15 @@ class LyricsButton(Button, VoiceExtension):
ClientAsync(ym_token), # type: ignore # Async client can be used here
))
lyrics = await track.get_lyrics_async()
try:
lyrics = await track.get_lyrics_async()
except yandex_music.exceptions.NotFoundError:
logging.debug('Lyrics not found')
await interaction.respond("❌ Текст песни не найден. Яндекс нам соврал (опять)!", delete_after=15, ephemeral=True)
return
if not lyrics:
logging.debug('Lyrics not found')
return
embed = Embed(
@@ -137,6 +145,36 @@ class LyricsButton(Button, VoiceExtension):
embed.add_field(name='', value=subtext, inline=False)
await interaction.respond(embed=embed, ephemeral=True)
class MyVibeButton(Button, VoiceExtension):
def __init__(self, **kwargs):
Button.__init__(self, **kwargs)
VoiceExtension.__init__(self, None)
async def callback(self, interaction: Interaction) -> None:
logging.info('[VIBE] My vibe button callback')
if not await self.voice_check(interaction):
return
if not interaction.guild_id:
logging.warning('[VIBE] No guild id in button callback')
return
track = self.db.get_track(interaction.guild_id, 'current')
if track:
logging.info(f"[VIBE] Playing vibe for track '{track["id"]}'")
await self.update_vibe(
interaction,
'track',
track['id'],
button_callback=True
)
else:
logging.info('[VIBE] Playing on your wave')
await self.update_vibe(
interaction,
'user',
'onyourwave',
button_callback=True
)
class MenuView(View, VoiceExtension):
@@ -156,8 +194,9 @@ class MenuView(View, VoiceExtension):
self.like_button = LikeButton(style=ButtonStyle.secondary, emoji='❤️', row=1)
self.lyrics_button = LyricsButton(style=ButtonStyle.secondary, emoji='📋', row=1)
self.vibe_button = MyVibeButton(style=ButtonStyle.secondary, emoji='🌊', row=1)
async def init(self) -> Self:
async def init(self, *, disable: bool = False) -> Self:
current_track = self.guild['current_track']
likes = await self.get_likes(self.ctx)
@@ -177,6 +216,10 @@ class MenuView(View, VoiceExtension):
self.add_item(self.like_button)
self.add_item(self.lyrics_button)
self.add_item(self.vibe_button)
if disable:
self.disable_all_items()
return self
@@ -186,7 +229,7 @@ class MenuView(View, VoiceExtension):
return
if self.guild['current_menu']:
self.db.update(self.ctx.guild_id, {'current_menu': None, 'previous_tracks': []})
self.db.update(self.ctx.guild_id, {'current_menu': None, 'previous_tracks': [], 'vibing': False})
message = await self.get_menu_message(self.ctx, self.guild['current_menu'])
if message:
await message.delete()