impr: Further code review.

This commit is contained in:
Lemon4ksan
2025-01-25 14:09:35 +03:00
parent 3adcde37eb
commit 6a20ab11d1
9 changed files with 241 additions and 134 deletions

View File

@@ -11,9 +11,9 @@ from yandex_music import ClientAsync as YMClient
from yandex_music import Track, Album, Artist, Playlist
from MusicBot.database import BaseUsersDatabase, BaseGuildsDatabase
from MusicBot.cogs.utils.find import ListenView, generate_item_embed
from MusicBot.cogs.utils.misc import generate_playlists_embed, generate_likes_embed
from MusicBot.cogs.utils.views import MyPlaylists
from MusicBot.ui import ListenView, MyPlaylists, generate_playlists_embed
from MusicBot.cogs.utils.embeds import generate_item_embed
def setup(bot):
bot.add_cog(General(bot))
@@ -38,17 +38,17 @@ class General(Cog):
logging.info(f"Help command invoked by {ctx.user.id} for command '{command}'")
response_message = None
embed = discord.Embed(
title='Помощь',
color=0xfed42b
)
embed.set_author(name='YandexMusic')
embed.description = '__Использование__\n'
embed.set_author(name='Помощь')
if command == 'all':
embed.description = ("Данный бот позволяет вам слушать музыку из вашего аккаунта Yandex Music.\n"
"Зарегистрируйте свой токен с помощью /login. Его можно получить [здесь](https://github.com/MarshalX/yandex-music-api/discussions/513).\n"
"Для получения помощи для конкретной команды, введите /help <команда>.\n\n"
"**Для доп. помощи, зайдите на [сервер любителей Яндекс Музыки](https://discord.gg/gkmFDaPMeC).**")
embed.title = 'Помощь'
embed.add_field(
name='__Основные команды__',
@@ -64,7 +64,6 @@ class General(Cog):
"""
)
embed.set_author(name='YandexMusic')
embed.set_footer(text='©️ Bananchiki')
elif command == 'account':
embed.description += ("Ввести токен от Яндекс Музыки. Его можно получить [здесь](https://github.com/MarshalX/yandex-music-api/discussions/513).\n"
@@ -87,7 +86,7 @@ class General(Cog):
elif command == 'settings':
embed.description += ("Получить текущие настройки.\n```/settings show```\n"
"Разрешить или запретить воспроизведение Explicit треков и альбомов. Если автор или плейлист содержат Explicit треки, убираются кнопки для доступа к ним.\n```/settings explicit```\n"
"Разрешить или запретить создание меню проигрывателя, даже если в канале больше одного человека.\n```/settings menu```\n"
"Разрешить или запретить создание меню проигрывателя, когда в канале больше одного человека.\n```/settings menu```\n"
"Разрешить или запретить голосование.\n```/settings vote <тип голосования>```\n"
"`Примечание`: Только пользователи с разрешением управления каналом могут менять настройки.")
elif command == 'track':
@@ -99,7 +98,7 @@ class General(Cog):
elif command == 'voice':
embed.description += ("Присоединить бота в голосовой канал. Требует разрешения управления каналом.\n ```/voice join```\n"
"Заставить бота покинуть голосовой канал. Требует разрешения управления каналом.\n ```/voice leave```\n"
"Создать меню проигрывателя. Доступно только если вы единственный в голосовом канале.\n```/voice menu```")
"Создать меню проигрывателя. Доступность зависит от настроек сервера. По умолчанию работает только когда в канале один человек.\n```/voice menu```")
else:
response_message = '❌ Неизвестная команда.'
embed = None
@@ -154,7 +153,7 @@ class General(Cog):
real_tracks = await gather(*[track_short.fetch_track_async() for track_short in likes.tracks], return_exceptions=True)
tracks = [track for track in real_tracks if not isinstance(track, BaseException)] # Can't fetch user tracks
embed = generate_likes_embed(tracks)
embed = await generate_item_embed(tracks)
logging.info(f"Successfully fetched likes for user {ctx.user.id}")
await ctx.respond(embed=embed, view=ListenView(tracks))

View File

@@ -0,0 +1,7 @@
from .embeds import generate_item_embed
from .voice_extension import VoiceExtension
__all__ = [
"generate_item_embed",
"VoiceExtension",
]

View File

@@ -1,4 +1,5 @@
from typing import Any, cast
import logging
from typing import cast
from math import ceil
from os import getenv
@@ -9,7 +10,31 @@ from PIL import Image
from yandex_music import Track, Album, Artist, Playlist, Label
from discord import Embed
def generate_likes_embed(tracks: list[Track]) -> Embed:
async def generate_item_embed(item: Track | Album | Artist | Playlist | list[Track]) -> Embed:
"""Generate item embed.
Args:
item (yandex_music.Track | yandex_music.Album | yandex_music.Artist | yandex_music.Playlist): Item to be processed.
Returns:
discord.Embed: Item embed.
"""
logging.debug(f"Generating embed for type: '{type(item).__name__}'")
if isinstance(item, Track):
return await _generate_track_embed(item)
elif isinstance(item, Album):
return await _generate_album_embed(item)
elif isinstance(item, Artist):
return await _generate_artist_embed(item)
elif isinstance(item, Playlist):
return await _generate_playlist_embed(item)
elif isinstance(item, list):
return _generate_likes_embed(item)
else:
raise ValueError(f"Unknown item type: {type(item).__name__}")
def _generate_likes_embed(tracks: list[Track]) -> Embed:
track_count = len(tracks)
cover_url = "https://avatars.yandex.net/get-music-user-playlist/11418140/favorit-playlist-cover.bb48fdb9b9f4/300x300"
@@ -34,37 +59,7 @@ def generate_likes_embed(tracks: list[Track]) -> Embed:
return embed
def generate_playlists_embed(page: int, playlists: list[tuple[str, int]]) -> Embed:
count = 15 * page
length = len(playlists)
embed = Embed(
title=f"Всего плейлистов: {length}",
color=0xfed42b
)
embed.set_author(name="Ваши плейлисты")
embed.set_footer(text=f"Страница {page + 1} из {ceil(length / 10)}")
for playlist in playlists[count:count + 10]:
embed.add_field(name=playlist[0], value=f"{playlist[1]} треков", inline=False)
return embed
def generate_queue_embed(page: int, tracks_list: list[dict[str, Any]]) -> Embed:
count = 15 * page
length = len(tracks_list)
embed = Embed(
title=f"Всего: {length}",
color=0xfed42b,
)
embed.set_author(name="Очередь треков")
embed.set_footer(text=f"Страница {page + 1} из {ceil(length / 15)}")
for i, track in enumerate(tracks_list[count:count + 15], start=1 + count):
duration = track['duration_ms']
if duration:
duration_m = duration // 60000
duration_s = ceil(duration / 1000) - duration_m * 60
embed.add_field(name=f"{i} - {track['title']} - {duration_m}:{duration_s:02d}", value="", inline=False)
return embed
async def generate_track_embed(track: Track) -> Embed:
async def _generate_track_embed(track: Track) -> Embed:
title = cast(str, track.title)
avail = cast(bool, track.available)
artists = track.artists_name()
@@ -78,7 +73,7 @@ async def generate_track_embed(track: Track) -> Embed:
artist = track.artists[0]
cover_url = track.get_cover_url('400x400')
color = await get_average_color_from_url(cover_url)
color = await _get_average_color_from_url(cover_url)
if explicit:
explicit_eid = getenv('EXPLICIT_EID')
@@ -131,7 +126,7 @@ async def generate_track_embed(track: Track) -> Embed:
return embed
async def generate_album_embed(album: Album) -> Embed:
async def _generate_album_embed(album: Album) -> Embed:
title = cast(str, album.title)
track_count = album.track_count
artists = album.artists_name()
@@ -146,7 +141,7 @@ async def generate_album_embed(album: Album) -> Embed:
artist = album.artists[0]
cover_url = album.get_cover_url('400x400')
color = await get_average_color_from_url(cover_url)
color = await _get_average_color_from_url(cover_url)
if isinstance(album.labels[0], Label):
labels = [cast(Label, label).name for label in album.labels]
@@ -204,7 +199,7 @@ async def generate_album_embed(album: Album) -> Embed:
return embed
async def generate_artist_embed(artist: Artist) -> Embed:
async def _generate_artist_embed(artist: Artist) -> Embed:
name = cast(str, artist.name)
likes_count = artist.likes_count
avail = cast(bool, artist.available)
@@ -217,7 +212,7 @@ async def generate_artist_embed(artist: Artist) -> Embed:
cover_url = artist.get_op_image_url('400x400')
else:
cover_url = artist.cover.get_url(size='400x400')
color = await get_average_color_from_url(cover_url)
color = await _get_average_color_from_url(cover_url)
embed = Embed(
title=name,
@@ -249,7 +244,7 @@ async def generate_artist_embed(artist: Artist) -> Embed:
return embed
async def generate_playlist_embed(playlist: Playlist) -> Embed:
async def _generate_playlist_embed(playlist: Playlist) -> Embed:
title = cast(str, playlist.title)
track_count = playlist.track_count
avail = cast(bool, playlist.available)
@@ -272,7 +267,7 @@ async def generate_playlist_embed(playlist: Playlist) -> Embed:
continue
if cover_url:
color = await get_average_color_from_url(cover_url)
color = await _get_average_color_from_url(cover_url)
embed = Embed(
title=title,
@@ -303,7 +298,7 @@ async def generate_playlist_embed(playlist: Playlist) -> Embed:
return embed
async def get_average_color_from_url(url: str) -> int:
async def _get_average_color_from_url(url: str) -> int:
"""Get image from url and calculate its average color to use in embeds.
Args:

View File

@@ -2,12 +2,12 @@ import asyncio
import logging
from typing import Any, Literal, cast
from yandex_music import Track, ClientAsync
from yandex_music import Track, TrackShort, ClientAsync
import discord
from discord import Interaction, ApplicationContext, RawReactionActionEvent
from MusicBot.cogs.utils.misc import generate_track_embed
from MusicBot.cogs.utils import generate_item_embed
from MusicBot.database import VoiceGuildsDatabase, BaseUsersDatabase
class VoiceExtension:
@@ -17,7 +17,7 @@ class VoiceExtension:
self.db = VoiceGuildsDatabase()
self.users_db = BaseUsersDatabase()
async def update_player_embed(self, ctx: ApplicationContext | Interaction | RawReactionActionEvent, player_mid: int) -> bool:
async def update_menu_embed(self, ctx: ApplicationContext | Interaction | RawReactionActionEvent, player_mid: int) -> bool:
"""Update current player message by its id. Return True if updated, False if not.
Args:
@@ -59,7 +59,7 @@ class VoiceExtension:
current_track,
client=ClientAsync(token) # type: ignore # Async client can be used here.
))
embed = await generate_track_embed(track)
embed = await generate_item_embed(track)
if isinstance(ctx, Interaction) and ctx.message and ctx.message.id == player_mid:
# If interaction from player buttons
@@ -230,7 +230,7 @@ class VoiceExtension:
player = guild['current_player']
if player is not None:
await self.update_player_embed(ctx, player)
await self.update_menu_embed(ctx, player)
return track.title
@@ -369,6 +369,35 @@ class VoiceExtension:
return None
async def get_likes(self, ctx: ApplicationContext | Interaction) -> list[TrackShort] | None:
"""Get liked tracks. Return list of tracks on success.
Return None if no token found.
Args:
ctx (ApplicationContext | Interaction): Context.
Returns:
list[Track] | None: List of tracks or None.
"""
if not ctx.guild or not ctx.user:
logging.warning("Guild or User not found in context inside 'like_track'")
return None
current_track = self.db.get_track(ctx.guild.id, 'current')
token = self.users_db.get_ym_token(ctx.user.id)
if not current_track or not token:
logging.debug("Current track or token not found")
return None
client = await ClientAsync(token).init()
likes = await client.users_likes_tracks()
if not likes:
logging.debug("No likes found")
return None
return likes.tracks
async def like_track(self, ctx: ApplicationContext | Interaction) -> str | Literal['TRACK REMOVED'] | None:
"""Like current track. Return track title on success.
@@ -389,9 +418,8 @@ class VoiceExtension:
return None
client = await ClientAsync(token).init()
likes = await client.users_likes_tracks()
likes = await self.get_likes(ctx)
if not likes:
logging.debug("No likes found")
return None
ym_track = cast(Track, Track.de_json(
@@ -399,7 +427,7 @@ class VoiceExtension:
client=client # type: ignore # Async client can be used here.
)
)
if ym_track.id not in [track.id for track in likes.tracks]:
if str(ym_track.id) not in [str(track.id) for track in likes]:
logging.debug("Track not found in likes. Adding...")
await ym_track.like_async()
return ym_track.title

View File

@@ -6,10 +6,8 @@ from discord.ext.commands import Cog
from yandex_music import Track, ClientAsync
from MusicBot.cogs.utils.voice_extension import VoiceExtension
from MusicBot.cogs.utils.player import Player
from MusicBot.cogs.utils.misc import generate_queue_embed, generate_track_embed
from MusicBot.cogs.utils.views import QueueView
from MusicBot.cogs.utils import VoiceExtension, generate_item_embed
from MusicBot.ui import MenuView, QueueView, generate_queue_embed
def setup(bot: discord.Bot):
bot.add_cog(Voice(bot))
@@ -31,6 +29,7 @@ class Voice(Cog, VoiceExtension):
gid = member.guild.id
guild = self.db.get_guild(gid)
discord_guild = await self.typed_bot.fetch_guild(gid)
current_player = self.db.get_current_player(gid)
channel = after.channel or before.channel
if not channel:
@@ -44,7 +43,6 @@ class Voice(Cog, VoiceExtension):
self.db.update(gid, {'previous_tracks': [], 'next_tracks': [], 'current_track': None, 'is_stopped': True})
vc.stop()
elif len(channel.members) > 2 and not guild['always_allow_menu']:
current_player = self.db.get_current_player(gid)
if current_player:
logging.info(f"Disabling current player for guild {gid} due to multiple members")
@@ -52,6 +50,7 @@ class Voice(Cog, VoiceExtension):
try:
message = await channel.fetch_message(current_player)
await message.delete()
await channel.send("Меню отключено из-за большого количества участников.", delete_after=15)
except (discord.NotFound, discord.Forbidden):
pass
@@ -201,7 +200,7 @@ class Voice(Cog, VoiceExtension):
return
if guild['current_track']:
embed = await generate_track_embed(
embed = await generate_item_embed(
Track.de_json(
guild['current_track'],
client=ClientAsync() # type: ignore # Async client can be used here.
@@ -215,10 +214,11 @@ class Voice(Cog, VoiceExtension):
if guild['current_player']:
logging.info(f"Deleteing old player menu {guild['current_player']} in guild {ctx.guild.id}")
message = await ctx.fetch_message(guild['current_player'])
await message.delete()
message = await self.get_player_message(ctx, guild['current_player'])
if message:
await message.delete()
interaction = cast(discord.Interaction, await ctx.respond(view=Player(ctx), embed=embed, delete_after=3600))
interaction = cast(discord.Interaction, await ctx.respond(view=await MenuView(ctx).init(), embed=embed, delete_after=3600))
response = await interaction.original_response()
self.db.update(ctx.guild.id, {'current_player': response.id})
@@ -306,7 +306,7 @@ class Voice(Cog, VoiceExtension):
player = self.db.get_current_player(ctx.guild.id)
if player:
await self.update_player_embed(ctx, player)
await self.update_menu_embed(ctx, player)
logging.info(f"Track paused in guild {ctx.guild.id}")
await ctx.respond("Воспроизведение приостановлено.", delete_after=15, ephemeral=True)
@@ -330,7 +330,7 @@ class Voice(Cog, VoiceExtension):
vc.resume()
player = self.db.get_current_player(ctx.guild.id)
if player:
await self.update_player_embed(ctx, player)
await self.update_menu_embed(ctx, player)
logging.info(f"Track resumed in guild {ctx.guild.id}")
await ctx.respond("Воспроизведение восстановлено.", delete_after=15, ephemeral=True)
else:

12
MusicBot/ui/__init__.py Normal file
View File

@@ -0,0 +1,12 @@
from .other import MyPlaylists, QueueView, generate_queue_embed, generate_playlists_embed
from .menu import MenuView
from .find import ListenView
__all__ = [
'MyPlaylists',
'QueueView',
'MenuView',
'ListenView',
'generate_queue_embed',
'generate_playlists_embed'
]

View File

@@ -5,10 +5,9 @@ import discord
from yandex_music import Track, Album, Artist, Playlist
from discord.ui import View, Button, Item
from discord import ButtonStyle, Interaction, Embed
from discord import ButtonStyle, Interaction
from MusicBot.cogs.utils.voice_extension import VoiceExtension
from MusicBot.cogs.utils.misc import generate_track_embed, generate_album_embed, generate_artist_embed, generate_playlist_embed
class PlayButton(Button, VoiceExtension):
def __init__(self, item: Track | Album | Artist | Playlist | list[Track], **kwargs):
@@ -38,7 +37,6 @@ class PlayButton(Button, VoiceExtension):
action = 'add_track'
vote_message = f"{member.mention} хочет добавить трек **{self.item.title}** в очередь.\n\n Голосуйте за добавление."
response_message = f"Трек **{self.item.title}** был добавлен в очередь."
play_message = f"Сейчас играет: **{self.item.title}**!"
elif isinstance(self.item, Album):
album = await self.item.with_tracks_async()
@@ -51,7 +49,6 @@ class PlayButton(Button, VoiceExtension):
action = 'add_album'
vote_message = f"{member.mention} хочет добавить альбом **{self.item.title}** в очередь.\n\n Голосуйте за добавление."
response_message = f"Альбом **{self.item.title}** был добавлен в очередь."
play_message = f"Сейчас играет: **{self.item.title}**!"
elif isinstance(self.item, Artist):
artist_tracks = await self.item.get_tracks_async()
@@ -64,7 +61,6 @@ class PlayButton(Button, VoiceExtension):
action = 'add_artist'
vote_message = f"{member.mention} хочет добавить треки от **{self.item.name}** в очередь.\n\n Голосуйте за добавление."
response_message = f"Песни артиста **{self.item.name}** были добавлены в очередь."
play_message = f"Сейчас играет: **{self.item.name}**!"
elif isinstance(self.item, Playlist):
short_tracks = await self.item.fetch_tracks_async()
@@ -77,7 +73,6 @@ class PlayButton(Button, VoiceExtension):
action = 'add_playlist'
vote_message = f"{member.mention} хочет добавить плейлист **{self.item.title}** в очередь.\n\n Голосуйте за добавление."
response_message = f"Плейлист **{self.item.title}** был добавлен в очередь."
play_message = f"Сейчас играет: **{self.item.title}**!"
elif isinstance(self.item, list):
tracks = self.item.copy()
@@ -122,14 +117,14 @@ class PlayButton(Button, VoiceExtension):
track = tracks.pop(0)
self.db.modify_track(gid, tracks, 'next', 'extend')
await self.play_track(interaction, track)
response_message = f"Сейчас играет: **{tracks[0].title}**!"
response_message = f"Сейчас играет: **{track.title}**!"
current_player = None
if guild['current_player']:
current_player = await self.get_player_message(interaction, guild['current_player'])
if current_player and interaction.message:
logging.debug(f"Deleting interaction message '{interaction.message.id}': current player '{current_player.id}' found")
logging.debug(f"Deleting interaction message {interaction.message.id}: current player {current_player.id} found")
await interaction.message.delete()
else:
await interaction.respond(response_message, delete_after=15)
@@ -162,26 +157,4 @@ class ListenView(View):
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)
async def generate_item_embed(item: Track | Album | Artist | Playlist) -> Embed:
"""Generate item embed.
Args:
item (yandex_music.Track | yandex_music.Album | yandex_music.Artist | yandex_music.Playlist): Item to be processed.
Returns:
discord.Embed: Item embed.
"""
logging.debug(f"Generating embed for type: '{type(item).__name__}'")
if isinstance(item, Track):
return await generate_track_embed(item)
elif isinstance(item, Album):
return await generate_album_embed(item)
elif isinstance(item, Artist):
return await generate_artist_embed(item)
elif isinstance(item, Playlist):
return await generate_playlist_embed(item)
else:
raise ValueError(f"Unknown item type: {type(item).__name__}")
self.add_item(self.button3)

View File

@@ -1,7 +1,10 @@
import logging
from discord.ui import View, Button, Item
from discord import ButtonStyle, Interaction, ApplicationContext
from typing import Self, cast
from discord.ui import View, Button, Item
from discord import VoiceChannel, ButtonStyle, Interaction, ApplicationContext, RawReactionActionEvent, Embed
from yandex_music import Track, ClientAsync
from MusicBot.cogs.utils.voice_extension import VoiceExtension
class ToggleRepeatButton(Button, VoiceExtension):
@@ -10,13 +13,13 @@ class ToggleRepeatButton(Button, VoiceExtension):
VoiceExtension.__init__(self, None)
async def callback(self, interaction: Interaction) -> None:
logging.debug('Repeat button callback...')
logging.info('Repeat button callback...')
if not interaction.guild:
return
gid = interaction.guild.id
guild = self.db.get_guild(gid)
self.db.update(gid, {'repeat': not guild['repeat']})
await interaction.edit(view=Player(interaction))
await interaction.edit(view=await MenuView(interaction).init())
class ToggleShuffleButton(Button, VoiceExtension):
def __init__(self, **kwargs):
@@ -24,13 +27,13 @@ class ToggleShuffleButton(Button, VoiceExtension):
VoiceExtension.__init__(self, None)
async def callback(self, interaction: Interaction) -> None:
logging.debug('Shuffle button callback...')
logging.info('Shuffle button callback...')
if not interaction.guild:
return
gid = interaction.guild.id
guild = self.db.get_guild(gid)
self.db.update(gid, {'shuffle': not guild['shuffle']})
await interaction.edit(view=Player(interaction))
await interaction.edit(view=await MenuView(interaction).init())
class PlayPauseButton(Button, VoiceExtension):
def __init__(self, **kwargs):
@@ -38,7 +41,7 @@ class PlayPauseButton(Button, VoiceExtension):
VoiceExtension.__init__(self, None)
async def callback(self, interaction: Interaction) -> None:
logging.debug('Play/Pause button callback...')
logging.info('Play/Pause button callback...')
if not await self.voice_check(interaction):
return
@@ -63,7 +66,7 @@ class NextTrackButton(Button, VoiceExtension):
VoiceExtension.__init__(self, None)
async def callback(self, interaction: Interaction) -> None:
logging.debug('Next track button callback...')
logging.info('Next track button callback...')
if not await self.voice_check(interaction):
return
title = await self.next_track(interaction)
@@ -76,7 +79,7 @@ class PrevTrackButton(Button, VoiceExtension):
VoiceExtension.__init__(self, None)
async def callback(self, interaction: Interaction) -> None:
logging.debug('Previous track button callback...')
logging.info('Previous track button callback...')
if not await self.voice_check(interaction):
return
title = await self.prev_track(interaction)
@@ -87,40 +90,92 @@ class LikeButton(Button, VoiceExtension):
def __init__(self, **kwargs):
Button.__init__(self, **kwargs)
VoiceExtension.__init__(self, None)
async def callback(self, interaction: Interaction) -> None:
logging.info('Like button callback...')
if not await self.voice_check(interaction):
return
if not (vc := await self.get_voice_client(interaction)) or not vc.is_playing:
await interaction.respond("❌ Нет воспроизводимого трека.", delete_after=15, ephemeral=True)
await self.like_track(interaction)
await interaction.edit(view=await MenuView(interaction).init())
class LyricsButton(Button, VoiceExtension):
def __init__(self, **kwargs):
Button.__init__(self, **kwargs)
VoiceExtension.__init__(self, None)
async def callback(self, interaction: Interaction) -> None:
logging.debug('Like button callback...')
if await self.voice_check(interaction):
vc = await self.get_voice_client(interaction)
if not vc or not vc.is_playing:
await interaction.respond("Нет воспроизводимого трека.", delete_after=15, ephemeral=True)
result = await self.like_track(interaction)
if not result:
await interaction.respond("❌ Операция не удалась.", delete_after=15, ephemeral=True)
elif result == 'TRACK REMOVED':
await interaction.respond("Трек был удалён из избранного.", delete_after=15, ephemeral=True)
else:
await interaction.respond(f"Трек **{result}** был добавлен в избранное.", delete_after=15, ephemeral=True)
logging.info('Lyrics button callback...')
class Player(View, VoiceExtension):
if not await self.voice_check(interaction) or not interaction.guild_id or not interaction.user:
return
ym_token = self.users_db.get_ym_token(interaction.user.id)
current_track = self.db.get_track(interaction.guild_id, 'current')
if not current_track or not ym_token:
return
track = cast(Track, Track.de_json(
current_track,
ClientAsync(ym_token), # type: ignore # Async client can be used here
))
lyrics = await track.get_lyrics_async()
if not lyrics:
return
embed = Embed(
title=track.title,
description='**Текст песни**',
color=0xfed42b,
)
text = await lyrics.fetch_lyrics_async()
for subtext in text.split('\n\n'):
embed.add_field(name='', value=subtext, inline=False)
await interaction.respond(embed=embed, ephemeral=True)
class MenuView(View, VoiceExtension):
def __init__(self, ctx: ApplicationContext | Interaction, *items: Item, timeout: float | None = 3600, disable_on_timeout: bool = True):
View.__init__(self, *items, timeout=timeout, disable_on_timeout=disable_on_timeout)
VoiceExtension.__init__(self, None)
if not ctx.guild:
if not ctx.guild_id:
return
guild = self.db.get_guild(ctx.guild.id)
self.repeat_button = ToggleRepeatButton(style=ButtonStyle.success if guild['repeat'] else ButtonStyle.secondary, emoji='🔂', row=0)
self.shuffle_button = ToggleShuffleButton(style=ButtonStyle.success if guild['shuffle'] else ButtonStyle.secondary, emoji='🔀', row=0)
self.ctx = ctx
self.guild = self.db.get_guild(ctx.guild_id)
self.repeat_button = ToggleRepeatButton(style=ButtonStyle.success if self.guild['repeat'] else ButtonStyle.secondary, emoji='🔂', row=0)
self.shuffle_button = ToggleShuffleButton(style=ButtonStyle.success if self.guild['shuffle'] else ButtonStyle.secondary, emoji='🔀', row=0)
self.play_pause_button = PlayPauseButton(style=ButtonStyle.primary, emoji='', row=0)
self.next_button = NextTrackButton(style=ButtonStyle.primary, emoji='', row=0)
self.prev_button = PrevTrackButton(style=ButtonStyle.primary, emoji='', row=0)
self.queue_button = Button(style=ButtonStyle.primary, emoji='📋', row=1)
self.like_button = LikeButton(style=ButtonStyle.secondary, emoji='❤️', row=1)
self.lyrics_button = LyricsButton(style=ButtonStyle.secondary, emoji='📋', row=1)
async def init(self) -> Self:
current_track = self.guild['current_track']
likes = await self.get_likes(self.ctx)
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)
if len(cast(VoiceChannel, self.ctx.channel).members) > 2:
self.like_button.disabled = True
elif likes and current_track and str(current_track['id']) in [str(like.id) for like in likes]:
self.like_button.style = ButtonStyle.success
if not current_track or not current_track['lyrics_available']:
self.lyrics_button.disabled = True
self.add_item(self.like_button)
self.add_item(self.lyrics_button)
return self

View File

@@ -1,8 +1,46 @@
from math import ceil
from typing import Any
from discord.ui import View, Button, Item
from discord import ButtonStyle, Interaction, Embed
from MusicBot.cogs.utils.voice_extension import VoiceExtension
from discord.ui import View, Button, Item
from discord import ButtonStyle, Interaction, ApplicationContext
from MusicBot.cogs.utils.voice_extension import VoiceExtension
from MusicBot.cogs.utils.misc import generate_playlists_embed, generate_queue_embed
def generate_playlists_embed(page: int, playlists: list[tuple[str, int]]) -> Embed:
count = 15 * page
length = len(playlists)
embed = Embed(
title=f"Всего плейлистов: {length}",
color=0xfed42b
)
embed.set_author(name="Ваши плейлисты")
embed.set_footer(text=f"Страница {page + 1} из {ceil(length / 10)}")
for playlist in playlists[count:count + 10]:
embed.add_field(name=playlist[0], value=f"{playlist[1]} треков", inline=False)
return embed
def generate_queue_embed(page: int, tracks_list: list[dict[str, Any]]) -> Embed:
count = 15 * page
length = len(tracks_list)
embed = Embed(
title=f"Всего: {length}",
color=0xfed42b,
)
embed.set_author(name="Очередь треков")
embed.set_footer(text=f"Страница {page + 1} из {ceil(length / 15)}")
for i, track in enumerate(tracks_list[count:count + 15], start=1 + count):
duration = track['duration_ms']
if duration:
duration_m = duration // 60000
duration_s = ceil(duration / 1000) - duration_m * 60
embed.add_field(name=f"{i} - {track['title']} - {duration_m}:{duration_s:02d}", value="", inline=False)
return embed
class MPNextButton(Button, VoiceExtension):
def __init__(self, **kwargs):
@@ -52,7 +90,7 @@ class MyPlaylists(View, VoiceExtension):
self.add_item(prev_button)
self.add_item(next_button)
class QNextButton(Button, VoiceExtension):
class QueueNextButton(Button, VoiceExtension):
def __init__(self, **kwargs):
Button.__init__(self, **kwargs)
VoiceExtension.__init__(self, None)
@@ -67,7 +105,7 @@ class QNextButton(Button, VoiceExtension):
embed = generate_queue_embed(page, tracks)
await interaction.edit(embed=embed, view=QueueView(interaction))
class QPrevButton(Button, VoiceExtension):
class QueuePrevButton(Button, VoiceExtension):
def __init__(self, **kwargs):
Button.__init__(self, **kwargs)
VoiceExtension.__init__(self, None)
@@ -93,8 +131,8 @@ class QueueView(View, VoiceExtension):
user = self.users_db.get_user(ctx.user.id)
count = 15 * user['queue_page']
next_button = QNextButton(style=ButtonStyle.primary, emoji='▶️')
prev_button = QPrevButton(style=ButtonStyle.primary, emoji='◀️')
next_button = QueueNextButton(style=ButtonStyle.primary, emoji='▶️')
prev_button = QueuePrevButton(style=ButtonStyle.primary, emoji='◀️')
if not tracks[count + 15:]:
next_button.disabled = True