From e320e10547943dcdb8e4bfc2f969a4f1b4a248ca Mon Sep 17 00:00:00 2001 From: Lemon4ksan Date: Wed, 19 Mar 2025 23:23:40 +0300 Subject: [PATCH] impr: Rework MenuView. --- MusicBot/cogs/utils/base_bot.py | 57 +++++---- MusicBot/cogs/utils/voice_extension.py | 70 ++++++----- MusicBot/ui/menu.py | 156 +++++++++++++++---------- 3 files changed, 153 insertions(+), 130 deletions(-) diff --git a/MusicBot/cogs/utils/base_bot.py b/MusicBot/cogs/utils/base_bot.py index 5262a9b..deb3275 100644 --- a/MusicBot/cogs/utils/base_bot.py +++ b/MusicBot/cogs/utils/base_bot.py @@ -6,14 +6,13 @@ import yandex_music.exceptions from yandex_music import ClientAsync as YMClient import discord -from discord.ui import View from discord import Interaction, ApplicationContext, RawReactionActionEvent, MISSING from MusicBot.database import VoiceGuildsDatabase, BaseUsersDatabase class BaseBot: - - menu_views: dict[int, View] = {} # Store menu views and delete them when needed to prevent memory leaks for after callbacks. + + menu_views: dict[int, Any] = {} # Store menu views and delete them when needed to prevent memory leaks for after callbacks. _ym_clients: dict[str, YMClient] = {} # Store YM clients to prevent creating new ones for each command. def __init__(self, bot: discord.Bot | None) -> None: @@ -106,10 +105,10 @@ class BaseBot: if not embed and response_type: if content: kwargs['description'] = content - embed = self.generate_response_embed(response_type, **kwargs) + embed = self.generate_response_embed(ctx, response_type, **kwargs) content = None - if not isinstance(ctx, RawReactionActionEvent) and ctx.response.is_done(): + if not isinstance(ctx, RawReactionActionEvent) and not view and ctx.response.is_done(): view = MISSING if not isinstance(ctx, RawReactionActionEvent): @@ -176,41 +175,37 @@ class BaseBot: return guild['current_viber_id'] return ctx.user_id if isinstance(ctx, discord.RawReactionActionEvent) else ctx.user.id if ctx.user else None - - async def update_menu_views_dict( - self, - ctx: ApplicationContext | Interaction | RawReactionActionEvent, - *, - disable: bool = False - ) -> None: - """Genereate a new menu view and update the `menu_views` dict. This prevents creating multiple menu views for the same guild. - Use guild id as a key to access menu view. - - Args: - ctx (ApplicationContext | Interaction | RawReactionActionEvent): Context - guild (ExplicitGuild): Guild. - disable (bool, optional): Disable menu. Defaults to False. - """ - logging.debug(f"[BASE_BOT] Updating menu views dict for guild {ctx.guild_id}") + + async def init_menu_view(self, ctx: ApplicationContext | Interaction | RawReactionActionEvent, gid: int, *, disable: bool = False) -> None: from MusicBot.ui import MenuView - - if not ctx.guild_id: - logging.warning("[BASE_BOT] Guild not found") - return - - if ctx.guild_id in self.menu_views: - self.menu_views[ctx.guild_id].stop() - - self.menu_views[ctx.guild_id] = await MenuView(ctx).init(disable=disable) + self.menu_views[gid] = await MenuView(ctx).init(disable=disable) def generate_response_embed( self, + ctx: ApplicationContext | Interaction | RawReactionActionEvent, embed_type: Literal['info', 'success', 'error'] = 'info', **kwargs: Any ) -> discord.Embed: + if isinstance(ctx, Interaction): + name = ctx.client.user.name if ctx.client.user else None + icon_url = ctx.client.user.avatar.url if ctx.client.user and ctx.client.user.avatar else None + elif isinstance(ctx, ApplicationContext): + name = ctx.bot.user.name if ctx.bot.user else None + icon_url = ctx.bot.user.avatar.url if ctx.bot.user and ctx.bot.user.avatar else None + elif self.bot: + name = self.bot.user.name if self.bot.user else None + icon_url = self.bot.user.avatar.url if self.bot.user and self.bot.user.avatar else None + else: + name = icon_url = None + + if not name: + name = 'YandexMusic' + if not icon_url: + icon_url="https://github.com/Lemon4ksan/YandexMusicDiscordBot/blob/main/assets/Logo.png?raw=true" + embed = discord.Embed(**kwargs) - embed.set_author(name='YandexMusic', icon_url="https://github.com/Lemon4ksan/YandexMusicDiscordBot/blob/main/assets/Logo.png?raw=true") + embed.set_author(name=name, icon_url=icon_url) if embed_type == 'info': embed.color = 0xfed42b diff --git a/MusicBot/cogs/utils/voice_extension.py b/MusicBot/cogs/utils/voice_extension.py index 0c3c9a7..8ad7399 100644 --- a/MusicBot/cogs/utils/voice_extension.py +++ b/MusicBot/cogs/utils/voice_extension.py @@ -8,7 +8,7 @@ import yandex_music.exceptions from yandex_music import Track, TrackShort, ClientAsync as YMClient import discord -from discord import Interaction, ApplicationContext, RawReactionActionEvent, VoiceChannel +from discord import Interaction, ApplicationContext, RawReactionActionEvent from MusicBot.cogs.utils.base_bot import BaseBot from MusicBot.cogs.utils import generate_item_embed @@ -63,11 +63,9 @@ class VoiceExtension(BaseBot): if guild['current_menu']: logging.info(f"[VC_EXT] Deleting old menu message {guild['current_menu']} in guild {ctx.guild_id}") - if (message := await self.get_menu_message(ctx, guild['current_menu'])): - await message.delete() - - await self.update_menu_views_dict(ctx, disable=disable) + await self._delete_menu_message(ctx, guild['current_menu'], ctx.guild_id) + await self.init_menu_view(ctx, ctx.guild_id, disable=disable) interaction = await self.respond(ctx, embed=embed, view=self.menu_views[ctx.guild_id]) response = await interaction.original_response() if isinstance(interaction, discord.Interaction) else interaction @@ -120,7 +118,6 @@ class VoiceExtension(BaseBot): Args: ctx (ApplicationContext | Interaction | RawReactionActionEvent): Context. - menu_mid (int): Id of the menu message to update. Defaults to None. menu_message (discord.Message | None): Message to update. If None, fetches menu from channel using `menu_mid`. Defaults to None. button_callback (bool, optional): Should be True if the function is being called from button callback. Defaults to False. @@ -174,7 +171,7 @@ class VoiceExtension(BaseBot): else: embed.remove_footer() - await self.update_menu_views_dict(ctx) + await self.menu_views[ctx.guild_id].update() try: if isinstance(ctx, Interaction) and button_callback: # If interaction from menu buttons @@ -193,7 +190,6 @@ class VoiceExtension(BaseBot): self, ctx: ApplicationContext | Interaction | RawReactionActionEvent, *, - menu_message: discord.Message | None = None, button_callback: bool = False, disable: bool = False ) -> bool: @@ -201,8 +197,6 @@ class VoiceExtension(BaseBot): Args: ctx (ApplicationContext | Interaction | RawReactionActionEvent): Context. - guild (ExplicitGuild): Guild data. - menu_message (discord.Message | None, optional): Menu message to update. Defaults to None. button_callback (bool, optional): If True, the interaction is from a button callback. Defaults to False. disable (bool, optional): Disable the view if True. Defaults to False. @@ -210,29 +204,33 @@ class VoiceExtension(BaseBot): bool: True if the view was updated, False otherwise. """ logging.debug("[VC_EXT] Updating menu view") - + if not ctx.guild_id: - logging.warning("[VC_EXT] Guild ID not found in context inside 'update_menu_view'") + logging.warning("[VC_EXT] Guild ID not found in context") return False - if not menu_message: - guild = await self.db.get_guild(ctx.guild_id, projection={'current_menu': 1}) - if not guild['current_menu']: - return False + guild = await self.db.get_guild(ctx.guild_id, projection={'current_menu': 1}) - menu_message = await self.get_menu_message(ctx, guild['current_menu']) if not menu_message else menu_message - - if not menu_message: + if not guild['current_menu']: + logging.warning("[VC_EXT] Current menu not found in guild data") return False - await self.update_menu_views_dict(ctx, disable=disable) + if ctx.guild_id not in self.menu_views: + logging.debug("[VC_EXT] Creating new menu view") + await self.init_menu_view(ctx, ctx.guild_id, disable=disable) + + view = self.menu_views[ctx.guild_id] + await view.update(disable=disable) + try: if isinstance(ctx, Interaction) and button_callback: # If interaction from menu buttons - await ctx.edit(view=self.menu_views[ctx.guild_id]) + await ctx.edit(view=view) else: # If interaction from other buttons or commands. They should have their own response. - await menu_message.edit(view=self.menu_views[ctx.guild_id]) + if (menu_message := await self.get_menu_message(ctx, guild['current_menu'])): + await menu_message.edit(view=view) + except discord.DiscordException as e: logging.warning(f"[VC_EXT] Error while updating menu view: {e}") return False @@ -411,7 +409,6 @@ class VoiceExtension(BaseBot): track: Track | dict[str, Any], *, vc: discord.VoiceClient | None = None, - menu_message: discord.Message | None = None, button_callback: bool = False, ) -> str | None: """Play `track` in the voice channel. Avoids additional vibe feedback used in `next_track` and `previous_track`. @@ -421,7 +418,6 @@ class VoiceExtension(BaseBot): ctx (ApplicationContext | Interaction | RawReactionActionEvent): Context. track (dict[str, Any]): Track to play. vc (discord.VoiceClient | None, optional): Voice client. Defaults to None. - menu_message (discord.Message | None, optional): Menu message to update. Defaults to None. button_callback (bool, optional): Should be True if the function is being called from button callback. Defaults to False. Returns: @@ -444,7 +440,6 @@ class VoiceExtension(BaseBot): ctx, track, vc=vc, - menu_message=menu_message, button_callback=button_callback ) @@ -501,7 +496,6 @@ class VoiceExtension(BaseBot): vc: discord.VoiceClient | None = None, *, after: bool = False, - menu_message: discord.Message | None = None, button_callback: bool = False ) -> str | None: """Switch to the next track in the queue. Return track title on success. @@ -524,9 +518,12 @@ class VoiceExtension(BaseBot): logging.warning("[VC_EXT] Guild ID or User ID not found in context inside 'next_track'") return None - guild = await self.db.get_guild(ctx.guild_id, projection={'shuffle': 1, 'repeat': 1, 'is_stopped': 1, 'current_menu': 1, 'vibing': 1, 'current_track': 1}) + guild = await self.db.get_guild(ctx.guild_id, projection={ + 'shuffle': 1, 'repeat': 1, 'is_stopped': 1, + 'current_menu': 1, 'vibing': 1, 'current_track': 1 + }) - if guild['is_stopped'] and after: + if after and guild['is_stopped']: logging.debug("[VC_EXT] Playback is stopped, skipping after callback.") return None @@ -534,8 +531,9 @@ class VoiceExtension(BaseBot): logging.debug("[VC_EXT] Adding current track to history") await self.db.modify_track(ctx.guild_id, guild['current_track'], 'previous', 'insert') - if after and not await self.update_menu_view(ctx, menu_message=menu_message, disable=True): - await self.respond(ctx, "error", "Не удалось обновить меню.", ephemeral=True, delete_after=15) + if after and guild['current_menu']: + if not await self.update_menu_view(ctx, button_callback=button_callback, disable=True): + await self.respond(ctx, "error", "Не удалось обновить меню.", ephemeral=True, delete_after=15) if guild['vibing'] and guild['current_track']: await self.send_vibe_feedback(ctx, 'trackFinished' if after else 'skip', guild['current_track']) @@ -570,6 +568,9 @@ class VoiceExtension(BaseBot): logging.info("[VC_EXT] No next track found") if after: await self.db.update(ctx.guild_id, {'is_stopped': True, 'current_track': None}) + + if guild['current_menu']: + await self.update_menu_view(ctx, button_callback=button_callback) return None @@ -854,7 +855,6 @@ class VoiceExtension(BaseBot): track: Track, *, vc: discord.VoiceClient | None = None, - menu_message: discord.Message | None = None, button_callback: bool = False, retry: bool = False ) -> str | None: @@ -865,7 +865,6 @@ class VoiceExtension(BaseBot): ctx (ApplicationContext | Interaction | RawReactionActionEvent): Context. track (Track): Track to play. vc (discord.VoiceClient | None): Voice client. - menu_message (discord.Message | None): Menu message. If None, fetches menu from channel using message id from database. Defaults to None. button_callback (bool): Should be True if the function is being called from button callback. Defaults to False. retry (bool): Whether the function is called again. @@ -887,7 +886,7 @@ class VoiceExtension(BaseBot): await self._download_track(ctx.guild_id, track) except yandex_music.exceptions.TimedOutError: if not retry: - return await self._play_track(ctx, track, vc=vc, menu_message=menu_message, button_callback=button_callback, retry=True) + return await self._play_track(ctx, track, vc=vc, button_callback=button_callback, retry=True) await self.respond(ctx, "error", "Не удалось загрузить трек. Попробуйте сбросить меню.", delete_after=15) logging.error(f"[VC_EXT] Failed to download track '{track.title}'") @@ -904,9 +903,8 @@ class VoiceExtension(BaseBot): await self.db.set_current_track(ctx.guild_id, track) - if menu_message or guild['current_menu']: - # Updating menu message before playing to prevent delay and avoid FFMPEG lags. - await self.update_menu_embed_and_view(ctx, menu_message=menu_message, button_callback=button_callback) + if guild['current_menu']: + await self.update_menu_embed_and_view(ctx, button_callback=button_callback) if not guild['vibing']: # Giving FFMPEG enough time to process the audio file diff --git a/MusicBot/ui/menu.py b/MusicBot/ui/menu.py index 062e463..275d6b8 100644 --- a/MusicBot/ui/menu.py +++ b/MusicBot/ui/menu.py @@ -13,13 +13,14 @@ from yandex_music import TrackLyrics, Playlist, ClientAsync as YMClient from MusicBot.cogs.utils import VoiceExtension class ToggleButton(Button, VoiceExtension): - def __init__(self, *args, **kwargs): + def __init__(self, root: 'MenuView', *args, **kwargs): super().__init__(*args, **kwargs) VoiceExtension.__init__(self, None) + self.root = root async def callback(self, interaction: Interaction) -> None: - callback_type = interaction.custom_id - if callback_type not in ('repeat', 'shuffle'): + + if (callback_type := interaction.custom_id) not in ('repeat', 'shuffle'): raise ValueError(f"Invalid callback type: '{callback_type}'") logging.info(f'[MENU] {callback_type.capitalize()} button callback') @@ -62,8 +63,10 @@ class ToggleButton(Button, VoiceExtension): await self.db.update(gid, {callback_type: not guild[callback_type]}) - if not await self.update_menu_view(interaction, button_callback=True): - await self.respond(interaction, "error", "Что-то пошло не так. Попробуйте снова.", delete_after=15, ephemeral=True) + button = self.root.repeat_button if callback_type == 'repeat' else self.root.shuffle_button + button.style = ButtonStyle.secondary if guild[callback_type] else ButtonStyle.success + + await interaction.edit(view=await self.root.update()) class PlayPauseButton(Button, VoiceExtension): def __init__(self, **kwargs): @@ -109,6 +112,11 @@ class PlayPauseButton(Button, VoiceExtension): ) return + if vc.is_paused(): + vc.resume() + else: + vc.pause() + try: embed = interaction.message.embeds[0] except IndexError: @@ -116,16 +124,19 @@ class PlayPauseButton(Button, VoiceExtension): return guild = await self.db.get_guild(interaction.guild_id, projection={'single_token_uid': 1}) - - if vc.is_paused(): - vc.resume() - if guild['single_token_uid'] and (user := await self.get_discord_user_by_id(interaction, guild['single_token_uid'])): + + if not vc.is_paused() and guild['single_token_uid']: + user = await self.get_discord_user_by_id(interaction, guild['single_token_uid']) + + if guild['single_token_uid'] and user: embed.set_footer(text=f"Используется токен {user.display_name}", icon_url=user.display_avatar.url) else: - embed.remove_footer() - else: - vc.pause() + embed.set_footer(text='Используется токен (неизвестный пользователь)') + + elif vc.is_paused(): embed.set_footer(text='Приостановлено') + else: + embed.remove_footer() await interaction.edit(embed=embed) @@ -135,8 +146,8 @@ class SwitchTrackButton(Button, VoiceExtension): VoiceExtension.__init__(self, None) async def callback(self, interaction: Interaction) -> None: - callback_type = interaction.custom_id - if callback_type not in ('next', 'previous'): + + if (callback_type := interaction.custom_id) not in ('next', 'previous'): raise ValueError(f"Invalid callback type: '{callback_type}'") if not (gid := interaction.guild_id) or not interaction.user: @@ -191,9 +202,10 @@ class SwitchTrackButton(Button, VoiceExtension): await self.respond(interaction, "error", "Что-то пошло не так. Попробуйте позже.", delete_after=15, ephemeral=True) class ReactionButton(Button, VoiceExtension): - def __init__(self, *args, **kwargs): + def __init__(self, root: 'MenuView', *args, **kwargs): super().__init__(*args, **kwargs) VoiceExtension.__init__(self, None) + self.root = root async def callback(self, interaction: Interaction): callback_type = interaction.custom_id @@ -212,30 +224,28 @@ class ReactionButton(Button, VoiceExtension): res = await self.react_track(interaction, callback_type) if callback_type == 'like' and res[0]: - await self.update_menu_views_dict(interaction) - await interaction.edit(view=self.menu_views[gid]) - await self.respond( - interaction, "success", - f"Трек был {'добавлен в понравившиеся.' if res[1] == 'added' else 'удалён из понравившихся.'}", - delete_after=15, ephemeral=True - ) + button = self.root.like_button + response_message = f"Трек был {'добавлен в понравившиеся.' if res[1] == 'added' else 'удалён из понравившихся.'}" elif callback_type == 'dislike' and res[0]: - if len(channel.members) == 2 and not await self.play_next_track(interaction, vc=vc, button_callback=True): - await self.respond(interaction, "info", "Воспроизведение приостановлено. Нет треков в очереди.", delete_after=15) + if len(channel.members) == 2: + await self.play_next_track(interaction, vc=vc, button_callback=True) + return - await self.update_menu_views_dict(interaction) - await interaction.edit(view=self.menu_views[gid]) - await self.respond( - interaction, "success", - f"Трек был {'добавлен в дизлайки.' if res[1] == 'added' else 'удалён из дизлайков.'}", - delete_after=15, ephemeral=True - ) + button = self.root.dislike_button + response_message =f"Трек был {'добавлен в дизлайки.' if res[1] == 'added' else 'удалён из дизлайков.'}" else: logging.debug(f"[VC_EXT] Failed to get {callback_type} tracks") await self.respond(interaction, "error", "Операция не удалась. Попробуйте позже.", delete_after=15, ephemeral=True) + return + + if len(channel.members) == 2: + button.style = ButtonStyle.success if res[1] == 'added' else ButtonStyle.secondary + await interaction.edit(view=await self.root.update()) + else: + await self.respond(interaction, "success", response_message, delete_after=15, ephemeral=True) async def react_track( self, @@ -586,8 +596,7 @@ class AddToPlaylistButton(Button, VoiceExtension): if not await self.voice_check(interaction) or not interaction.guild_id: return - current_track = await self.db.get_track(interaction.guild_id, 'current') - if not current_track: + if not await self.db.get_track(interaction.guild_id, 'current'): await self.respond(interaction, "error", "Нет воспроизводимого трека.", delete_after=15, ephemeral=True) return @@ -599,8 +608,7 @@ class AddToPlaylistButton(Button, VoiceExtension): await self.respond(interaction, "error", "Нет воспроизводимого трека.", delete_after=15, ephemeral=True) return - playlists = await client.users_playlists_list() - if not playlists: + if not (playlists := await client.users_playlists_list()): await self.respond(interaction, "error", "У вас нет плейлистов.", delete_after=15, ephemeral=True) return @@ -628,39 +636,58 @@ class MenuView(View, VoiceExtension): VoiceExtension.__init__(self, None) self.ctx = ctx - self.repeat_button = ToggleButton(style=ButtonStyle.secondary, emoji='🔂', row=0, custom_id='repeat') - self.shuffle_button = ToggleButton(style=ButtonStyle.secondary, emoji='🔀', row=0, custom_id='shuffle') + self.repeat_button = ToggleButton(self, style=ButtonStyle.secondary, emoji='🔂', row=0, custom_id='repeat') + self.shuffle_button = ToggleButton(self, style=ButtonStyle.secondary, emoji='🔀', row=0, custom_id='shuffle') self.play_pause_button = PlayPauseButton(style=ButtonStyle.primary, emoji='⏯', row=0) self.next_button = SwitchTrackButton(style=ButtonStyle.primary, emoji='⏭', row=0, custom_id='next') self.prev_button = SwitchTrackButton(style=ButtonStyle.primary, emoji='⏮', row=0, custom_id='previous') - self.like_button = ReactionButton(style=ButtonStyle.secondary, emoji='❤️', row=1, custom_id='like') - self.dislike_button = ReactionButton(style=ButtonStyle.secondary, emoji='💔', row=1, custom_id='dislike') + self.like_button = ReactionButton(self, style=ButtonStyle.secondary, emoji='❤️', row=1, custom_id='like') + self.dislike_button = ReactionButton(self, style=ButtonStyle.secondary, emoji='💔', row=1, custom_id='dislike') self.lyrics_button = LyricsButton(style=ButtonStyle.secondary, emoji='📋', row=1) self.add_to_playlist_button = AddToPlaylistButton(style=ButtonStyle.secondary, emoji='📁', row=1) self.vibe_button = MyVibeButton(style=ButtonStyle.secondary, emoji='🌊', row=1) self.vibe_settings_button = MyVibeSettingsButton(style=ButtonStyle.success, emoji='🛠', row=1) + + self.current_vibe_button: MyVibeButton | MyVibeSettingsButton = self.vibe_button async def init(self, *, disable: bool = False) -> Self: - if not self.ctx.guild_id: - return self - - self.guild = await self.db.get_guild(self.ctx.guild_id, projection={ - 'repeat': 1, 'shuffle': 1, 'current_track': 1, 'current_menu': 1, 'vibing': 1, 'single_token_uid': 1 - }) - - if self.guild['repeat']: - self.repeat_button.style = ButtonStyle.success - if self.guild['shuffle']: - self.shuffle_button.style = ButtonStyle.success - - current_track = self.guild['current_track'] + await self.update(disable=disable) self.add_item(self.repeat_button) self.add_item(self.prev_button) self.add_item(self.play_pause_button) self.add_item(self.next_button) self.add_item(self.shuffle_button) + self.add_item(self.like_button) + self.add_item(self.dislike_button) + self.add_item(self.lyrics_button) + self.add_item(self.add_to_playlist_button) + self.add_item(self.current_vibe_button) + + return self + + async def update(self, *, disable: bool = False) -> Self: + if not self.ctx.guild_id: + return self + + self.enable_all_items() + + self.guild = await self.db.get_guild(self.ctx.guild_id, projection={ + 'repeat': 1, 'shuffle': 1, 'current_track': 1, 'current_viber_id': 1, 'vibing': 1, 'single_token_uid': 1 + }) + + if self.guild['repeat']: + self.repeat_button.style = ButtonStyle.success + else: + self.repeat_button.style = ButtonStyle.secondary + + if self.guild['shuffle']: + self.shuffle_button.style = ButtonStyle.success + else: + self.shuffle_button.style = ButtonStyle.secondary + + current_track = self.guild['current_track'] if not isinstance(self.ctx, RawReactionActionEvent) \ and len(cast(VoiceChannel, self.ctx.channel).members) == 2 \ @@ -668,9 +695,17 @@ class MenuView(View, VoiceExtension): if current_track and str(current_track['id']) in [str(like.id) for like in await self.get_reacted_tracks(self.ctx, 'like')]: self.like_button.style = ButtonStyle.success + else: + self.like_button.style = ButtonStyle.secondary if current_track and str(current_track['id']) in [str(dislike.id) for dislike in await self.get_reacted_tracks(self.ctx, 'dislike')]: self.dislike_button.style = ButtonStyle.success + else: + self.dislike_button.style = ButtonStyle.secondary + + else: + self.like_button.style = ButtonStyle.secondary + self.dislike_button.style = ButtonStyle.secondary if not current_track: self.lyrics_button.disabled = True @@ -679,32 +714,27 @@ class MenuView(View, VoiceExtension): self.add_to_playlist_button.disabled = True elif not current_track['lyrics_available']: self.lyrics_button.disabled = True - + if self.guild['single_token_uid']: self.like_button.disabled = True self.dislike_button.disabled = True self.add_to_playlist_button.disabled = True - self.add_item(self.like_button) - self.add_item(self.dislike_button) - self.add_item(self.lyrics_button) - self.add_item(self.add_to_playlist_button) - if self.guild['vibing']: - self.add_item(self.vibe_settings_button) + self.current_vibe_button = self.vibe_settings_button else: - self.add_item(self.vibe_button) + self.current_vibe_button = self.vibe_button if disable: self.disable_all_items() return self - + async def on_timeout(self) -> None: logging.debug('[MENU] Menu timed out. Deleting menu message') if not self.ctx.guild_id: return - + if self.guild['current_menu']: await self.db.update(self.ctx.guild_id, { 'current_menu': None, 'repeat': False, 'shuffle': False, @@ -718,4 +748,4 @@ class MenuView(View, VoiceExtension): else: logging.debug('[MENU] No menu message found') - self.stop() + self.stop()