mirror of
https://github.com/deadcxap/YandexMusicDiscordBot.git
synced 2026-01-09 22:51:46 +03:00
impr: Further code review.
This commit is contained in:
@@ -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))
|
||||
|
||||
|
||||
7
MusicBot/cogs/utils/__init__.py
Normal file
7
MusicBot/cogs/utils/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from .embeds import generate_item_embed
|
||||
from .voice_extension import VoiceExtension
|
||||
|
||||
__all__ = [
|
||||
"generate_item_embed",
|
||||
"VoiceExtension",
|
||||
]
|
||||
@@ -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:
|
||||
@@ -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
|
||||
|
||||
@@ -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
12
MusicBot/ui/__init__.py
Normal 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'
|
||||
]
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user