mirror of
https://github.com/deadcxap/YandexMusicDiscordBot.git
synced 2026-01-10 09:41:46 +03:00
impr: Changed /account playlists functionality
This commit is contained in:
@@ -12,17 +12,18 @@ from yandex_music import Track, Album, Artist, Playlist
|
|||||||
|
|
||||||
from MusicBot.database import BaseUsersDatabase, BaseGuildsDatabase
|
from MusicBot.database import BaseUsersDatabase, BaseGuildsDatabase
|
||||||
|
|
||||||
from MusicBot.ui import ListenView, MyPlaylists, generate_playlists_embed
|
from MusicBot.ui import ListenView
|
||||||
from MusicBot.cogs.utils.embeds import generate_item_embed
|
from MusicBot.cogs.utils.embeds import generate_item_embed
|
||||||
|
|
||||||
|
users_db = BaseUsersDatabase()
|
||||||
|
|
||||||
def setup(bot):
|
def setup(bot):
|
||||||
bot.add_cog(General(bot))
|
bot.add_cog(General(bot))
|
||||||
|
|
||||||
async def get_search_suggestions(ctx: discord.AutocompleteContext) -> list[str]:
|
async def get_search_suggestions(ctx: discord.AutocompleteContext) -> list[str]:
|
||||||
if not ctx.interaction.user or not ctx.value or len(ctx.value) < 2:
|
if not ctx.interaction.user or not ctx.value or len(ctx.value) < 2:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
users_db = BaseUsersDatabase()
|
|
||||||
token = await users_db.get_ym_token(ctx.interaction.user.id)
|
token = await users_db.get_ym_token(ctx.interaction.user.id)
|
||||||
if not token:
|
if not token:
|
||||||
logging.info(f"[GENERAL] User {ctx.interaction.user.id} has no token")
|
logging.info(f"[GENERAL] User {ctx.interaction.user.id} has no token")
|
||||||
@@ -64,12 +65,30 @@ async def get_search_suggestions(ctx: discord.AutocompleteContext) -> list[str]:
|
|||||||
|
|
||||||
return res[:100]
|
return res[:100]
|
||||||
|
|
||||||
|
async def get_user_playlists_suggestions(ctx: discord.AutocompleteContext) -> list[str]:
|
||||||
|
if not ctx.interaction.user or not ctx.value or len(ctx.value) < 2:
|
||||||
|
return []
|
||||||
|
|
||||||
|
token = await users_db.get_ym_token(ctx.interaction.user.id)
|
||||||
|
if not token:
|
||||||
|
logging.info(f"[GENERAL] User {ctx.interaction.user.id} has no token")
|
||||||
|
return []
|
||||||
|
|
||||||
|
try:
|
||||||
|
client = await YMClient(token).init()
|
||||||
|
except yandex_music.exceptions.UnauthorizedError:
|
||||||
|
logging.info(f"[GENERAL] User {ctx.interaction.user.id} provided invalid token")
|
||||||
|
return []
|
||||||
|
|
||||||
|
playlists_list = await client.users_playlists_list()
|
||||||
|
return [playlist.title if playlist.title else 'Без названия' for playlist in playlists_list]
|
||||||
|
|
||||||
class General(Cog):
|
class General(Cog):
|
||||||
|
|
||||||
def __init__(self, bot: discord.Bot):
|
def __init__(self, bot: discord.Bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.db = BaseGuildsDatabase()
|
self.db = BaseGuildsDatabase()
|
||||||
self.users_db = BaseUsersDatabase()
|
self.users_db = users_db
|
||||||
|
|
||||||
account = discord.SlashCommandGroup("account", "Команды, связанные с аккаунтом.")
|
account = discord.SlashCommandGroup("account", "Команды, связанные с аккаунтом.")
|
||||||
|
|
||||||
@@ -106,7 +125,6 @@ class General(Cog):
|
|||||||
value="""`account`
|
value="""`account`
|
||||||
`find`
|
`find`
|
||||||
`help`
|
`help`
|
||||||
`like`
|
|
||||||
`queue`
|
`queue`
|
||||||
`settings`
|
`settings`
|
||||||
`track`
|
`track`
|
||||||
@@ -116,26 +134,23 @@ class General(Cog):
|
|||||||
embed.set_footer(text='©️ Bananchiki')
|
embed.set_footer(text='©️ Bananchiki')
|
||||||
elif command == 'account':
|
elif command == 'account':
|
||||||
embed.description += (
|
embed.description += (
|
||||||
"Ввести токен от Яндекс Музыки. Его можно получить [здесь](https://github.com/MarshalX/yandex-music-api/discussions/513).\n"
|
"Ввести токен Яндекс Музыки. Его можно получить [здесь](https://github.com/MarshalX/yandex-music-api/discussions/513).\n"
|
||||||
"```/account login <token>```\n"
|
"```/account login <token>```\n"
|
||||||
"Удалить токен из базы данных бота.\n```/account remove```\n"
|
"Удалить токен из базы данных бота.\n```/account remove```\n"
|
||||||
"Получить ваши плейлисты. Чтобы добавить плейлист в очередь, используйте команду /find.\n```/account playlists```\n"
|
"Получить ваш плейлист.\n```/account playlist <название>```\n"
|
||||||
"Получить плейлист «Мне нравится».\n```/account likes```\n"
|
"Получить плейлист «Мне нравится».\n```/account likes```\n"
|
||||||
|
"Получить ваши рекомендации.\n```/account recommendations <тип>```\n"
|
||||||
)
|
)
|
||||||
elif command == 'find':
|
elif command == 'find':
|
||||||
embed.description += (
|
embed.description += (
|
||||||
"Вывести информацию о треке (по умолчанию), альбоме, авторе или плейлисте. Позволяет добавить музыку в очередь. "
|
"Вывести информацию о треке (по умолчанию), альбоме, авторе или плейлисте. Позволяет добавить музыку в очередь. "
|
||||||
"В названии можно уточнить автора или версию. Возвращается лучшее совпадение.\n```/find <название> <тип>```"
|
"В названии можно уточнить автора через «-». Возвращается лучшее совпадение.\n```/find <тип> <название>```"
|
||||||
)
|
)
|
||||||
elif command == 'help':
|
elif command == 'help':
|
||||||
embed.description += (
|
embed.description += (
|
||||||
"Вывести список всех команд.\n```/help```\n"
|
"Вывести список всех команд.\n```/help```\n"
|
||||||
"Получить информацию о конкретной команде.\n```/help <команда>```"
|
"Получить информацию о конкретной команде.\n```/help <команда>```"
|
||||||
)
|
)
|
||||||
elif command == 'like':
|
|
||||||
embed.description += (
|
|
||||||
"Добавить трек в плейлист «Мне нравится». Пользовательские треки из этого плейлиста игнорируются.\n```/like```"
|
|
||||||
)
|
|
||||||
elif command == 'queue':
|
elif command == 'queue':
|
||||||
embed.description += (
|
embed.description += (
|
||||||
"Получить очередь треков. По 15 элементов на страницу.\n```/queue get```\n"
|
"Получить очередь треков. По 15 элементов на страницу.\n```/queue get```\n"
|
||||||
@@ -147,7 +162,8 @@ class General(Cog):
|
|||||||
"Получить текущие настройки.\n```/settings show```\n"
|
"Получить текущие настройки.\n```/settings show```\n"
|
||||||
"Разрешить или запретить воспроизведение Explicit треков и альбомов. Если автор или плейлист содержат Explicit треки, убираются кнопки для доступа к ним.\n```/settings explicit```\n"
|
"Разрешить или запретить воспроизведение Explicit треков и альбомов. Если автор или плейлист содержат Explicit треки, убираются кнопки для доступа к ним.\n```/settings explicit```\n"
|
||||||
"Разрешить или запретить создание меню проигрывателя, когда в канале больше одного человека.\n```/settings menu```\n"
|
"Разрешить или запретить создание меню проигрывателя, когда в канале больше одного человека.\n```/settings menu```\n"
|
||||||
"Разрешить или запретить голосование.\n```/settings vote <тип голосования>```\n"
|
"Разрешить или запретить голосование.\n```/settings vote <тип>```\n"
|
||||||
|
"Разрешить или запретить отключение/подключение бота к каналу участникам без прав управления каналом.\n```/settings connect```\n"
|
||||||
"`Примечание`: Только пользователи с разрешением управления каналом могут менять настройки."
|
"`Примечание`: Только пользователи с разрешением управления каналом могут менять настройки."
|
||||||
)
|
)
|
||||||
elif command == 'track':
|
elif command == 'track':
|
||||||
@@ -157,6 +173,7 @@ class General(Cog):
|
|||||||
"Приостановить текущий трек.\n```/track pause```\n"
|
"Приостановить текущий трек.\n```/track pause```\n"
|
||||||
"Возобновить текущий трек.\n```/track resume```\n"
|
"Возобновить текущий трек.\n```/track resume```\n"
|
||||||
"Прервать проигрывание, удалить историю, очередь и текущий плеер.\n ```/track stop```\n"
|
"Прервать проигрывание, удалить историю, очередь и текущий плеер.\n ```/track stop```\n"
|
||||||
|
"Добавить трек в плейлист «Мне нравится» или удалить его, если он уже там.\n```/track like```"
|
||||||
"Запустить Мою Волну по текущему треку.\n```/track vibe```"
|
"Запустить Мою Волну по текущему треку.\n```/track vibe```"
|
||||||
)
|
)
|
||||||
elif command == 'voice':
|
elif command == 'voice':
|
||||||
@@ -282,27 +299,57 @@ class General(Cog):
|
|||||||
|
|
||||||
await ctx.respond(embed=embed, view=view)
|
await ctx.respond(embed=embed, view=view)
|
||||||
|
|
||||||
@account.command(description="Получить ваши плейлисты.")
|
@account.command(description="Получить ваш плейлист.")
|
||||||
async def playlists(self, ctx: discord.ApplicationContext) -> None:
|
@discord.option(
|
||||||
|
"запрос",
|
||||||
|
parameter_name='name',
|
||||||
|
description="Название плейлиста.",
|
||||||
|
type=discord.SlashCommandOptionType.string,
|
||||||
|
autocomplete=discord.utils.basic_autocomplete(get_user_playlists_suggestions)
|
||||||
|
)
|
||||||
|
async def playlist(self, ctx: discord.ApplicationContext, name: str) -> None:
|
||||||
logging.info(f"[GENERAL] Playlists command invoked by user {ctx.user.id} in guild {ctx.guild_id}")
|
logging.info(f"[GENERAL] Playlists command invoked by user {ctx.user.id} in guild {ctx.guild_id}")
|
||||||
|
|
||||||
|
guild = await self.db.get_guild(ctx.guild_id, projection={'allow_explicit': 1})
|
||||||
token = await self.users_db.get_ym_token(ctx.user.id)
|
token = await self.users_db.get_ym_token(ctx.user.id)
|
||||||
if not token:
|
if not token:
|
||||||
|
logging.info(f"[GENERAL] No token found for user {ctx.user.id}")
|
||||||
await ctx.respond("❌ Укажите токен через /account login.", delete_after=15, ephemeral=True)
|
await ctx.respond("❌ Укажите токен через /account login.", delete_after=15, ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
client = await YMClient(token).init()
|
try:
|
||||||
|
client = await YMClient(token).init()
|
||||||
|
except yandex_music.exceptions.UnauthorizedError:
|
||||||
|
logging.info(f"[GENERAL] User {ctx.user.id} provided invalid token")
|
||||||
|
await ctx.respond("❌ Недействительный токен. Если это не так, попробуйте ещё раз.", delete_after=15, ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
playlists_list = await client.users_playlists_list()
|
playlists = await client.users_playlists_list()
|
||||||
playlists: list[tuple[str, int]] = [
|
|
||||||
(playlist.title if playlist.title else 'Без названия', playlist.track_count if playlist.track_count else 0) for playlist in playlists_list
|
|
||||||
]
|
|
||||||
|
|
||||||
await self.users_db.update(ctx.user.id, {'playlists': playlists, 'playlists_page': 0})
|
playlist = next((playlist for playlist in playlists if playlist.title == name), None)
|
||||||
embed = generate_playlists_embed(0, playlists)
|
if not playlist:
|
||||||
|
logging.info(f"[GENERAL] User {ctx.user.id} playlist '{name}' not found")
|
||||||
|
await ctx.respond("❌ Плейлист не найден.", delete_after=15, ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
logging.info(f"[GENERAL] Successfully fetched playlists for user {ctx.user.id}")
|
tracks = await playlist.fetch_tracks_async()
|
||||||
await ctx.respond(embed=embed, view=await MyPlaylists(ctx).init(), ephemeral=True)
|
if not tracks:
|
||||||
|
logging.info(f"[GENERAL] User {ctx.user.id} playlist '{name}' is empty")
|
||||||
|
await ctx.respond("❌ Плейлист пуст.", delete_after=15, ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
embed = await generate_item_embed(playlist)
|
||||||
|
view = ListenView(playlist)
|
||||||
|
|
||||||
|
for track_short in playlist.tracks:
|
||||||
|
track = cast(Track, track_short.track)
|
||||||
|
if (track.explicit or track.content_warning) and not guild['allow_explicit']:
|
||||||
|
logging.info(f"[GENERAL] User {ctx.user.id} search for '{name}' returned explicit content and is not allowed on this server")
|
||||||
|
embed.set_footer(text="Воспроизведение недоступно, так как в плейлисте присутствуют Explicit треки")
|
||||||
|
view = None
|
||||||
|
break
|
||||||
|
|
||||||
|
await ctx.respond(embed=embed, view=view)
|
||||||
|
|
||||||
@discord.slash_command(description="Найти контент и отправить информацию о нём. Возвращается лучшее совпадение.")
|
@discord.slash_command(description="Найти контент и отправить информацию о нём. Возвращается лучшее совпадение.")
|
||||||
@discord.option(
|
@discord.option(
|
||||||
@@ -310,7 +357,7 @@ class General(Cog):
|
|||||||
parameter_name='content_type',
|
parameter_name='content_type',
|
||||||
description="Тип контента для поиска.",
|
description="Тип контента для поиска.",
|
||||||
type=discord.SlashCommandOptionType.string,
|
type=discord.SlashCommandOptionType.string,
|
||||||
choices=['Трек', 'Альбом', 'Артист', 'Плейлист', 'Свой плейлист'],
|
choices=['Трек', 'Альбом', 'Артист', 'Плейлист'],
|
||||||
)
|
)
|
||||||
@discord.option(
|
@discord.option(
|
||||||
"запрос",
|
"запрос",
|
||||||
@@ -322,11 +369,10 @@ class General(Cog):
|
|||||||
async def find(
|
async def find(
|
||||||
self,
|
self,
|
||||||
ctx: discord.ApplicationContext,
|
ctx: discord.ApplicationContext,
|
||||||
content_type: Literal['Трек', 'Альбом', 'Артист', 'Плейлист', 'Свой плейлист'],
|
content_type: Literal['Трек', 'Альбом', 'Артист', 'Плейлист'],
|
||||||
name: str
|
name: str
|
||||||
) -> None:
|
) -> None:
|
||||||
# TODO: Improve explicit check by excluding bad tracks from the queue and not fully discard the artist/album/playlist.
|
# TODO: Improve explicit check by excluding bad tracks from the queue and not fully discard the artist/album/playlist.
|
||||||
# TODO: Move 'Свой плейлист' search to /account playlists command by using select menu.
|
|
||||||
|
|
||||||
logging.info(f"[GENERAL] Find command invoked by user {ctx.user.id} in guild {ctx.guild_id} for '{content_type}' with name '{name}'")
|
logging.info(f"[GENERAL] Find command invoked by user {ctx.user.id} in guild {ctx.guild_id} for '{content_type}' with name '{name}'")
|
||||||
|
|
||||||
@@ -344,85 +390,60 @@ class General(Cog):
|
|||||||
await ctx.respond("❌ Недействительный токен. Если это не так, попробуйте ещё раз.", delete_after=15, ephemeral=True)
|
await ctx.respond("❌ Недействительный токен. Если это не так, попробуйте ещё раз.", delete_after=15, ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
if content_type == 'Свой плейлист':
|
result = await client.search(name, nocorrect=True)
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
logging.warning(f"Failed to search for '{name}' for user {ctx.user.id}")
|
||||||
|
await ctx.respond("❌ Что-то пошло не так. Повторите попытку позже.", delete_after=15, ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
playlists = await client.users_playlists_list()
|
if content_type == 'Трек':
|
||||||
result = next((playlist for playlist in playlists if playlist.title == name), None)
|
content = result.tracks
|
||||||
if not result:
|
elif content_type == 'Альбом':
|
||||||
logging.info(f"[GENERAL] User {ctx.user.id} playlist '{name}' not found")
|
content = result.albums
|
||||||
await ctx.respond("❌ Плейлист не найден.", delete_after=15, ephemeral=True)
|
elif content_type == 'Артист':
|
||||||
return
|
content = result.artists
|
||||||
|
elif content_type == 'Плейлист':
|
||||||
tracks = await result.fetch_tracks_async()
|
content = result.playlists
|
||||||
|
|
||||||
|
if not content:
|
||||||
|
logging.info(f"[GENERAL] User {ctx.user.id} search for '{name}' returned no results")
|
||||||
|
await ctx.respond("❌ По запросу ничего не найдено.", delete_after=15, ephemeral=True)
|
||||||
|
return
|
||||||
|
content = content.results[0]
|
||||||
|
|
||||||
|
embed = await generate_item_embed(content)
|
||||||
|
view = ListenView(content)
|
||||||
|
|
||||||
|
if isinstance(content, (Track, Album)) and (content.explicit or content.content_warning) and not guild['allow_explicit']:
|
||||||
|
logging.info(f"[GENERAL] User {ctx.user.id} search for '{name}' returned explicit content and is not allowed on this server")
|
||||||
|
await ctx.respond("❌ Explicit контент запрещён на этом сервере.", delete_after=15, ephemeral=True)
|
||||||
|
return
|
||||||
|
elif isinstance(content, Artist):
|
||||||
|
tracks = await content.get_tracks_async()
|
||||||
if not tracks:
|
if not tracks:
|
||||||
logging.info(f"[GENERAL] User {ctx.user.id} playlist '{name}' is empty")
|
logging.info(f"[GENERAL] User {ctx.user.id} search for '{name}' returned no tracks")
|
||||||
await ctx.respond("❌ Плейлист пуст.", delete_after=15, ephemeral=True)
|
await ctx.respond("❌ Треки от этого исполнителя не найдены.", delete_after=15, ephemeral=True)
|
||||||
return
|
return
|
||||||
|
for track in tracks:
|
||||||
for track_short in tracks:
|
if (track.explicit or track.content_warning) and not guild['allow_explicit']:
|
||||||
|
logging.info(f"[GENERAL] User {ctx.user.id} search for '{name}' returned explicit content and is not allowed on this server")
|
||||||
|
view = None
|
||||||
|
embed.set_footer(text="Воспроизведение недоступно, так как у автора присутствуют Explicit треки")
|
||||||
|
break
|
||||||
|
elif isinstance(content, Playlist):
|
||||||
|
tracks = await content.fetch_tracks_async()
|
||||||
|
if not tracks:
|
||||||
|
logging.info(f"[GENERAL] User {ctx.user.id} search for '{name}' returned no tracks")
|
||||||
|
await ctx.respond("❌ Пустой плейлист.", delete_after=15, ephemeral=True)
|
||||||
|
return
|
||||||
|
for track_short in content.tracks:
|
||||||
track = cast(Track, track_short.track)
|
track = cast(Track, track_short.track)
|
||||||
if (track.explicit or track.content_warning) and not guild['allow_explicit']:
|
if (track.explicit or track.content_warning) and not guild['allow_explicit']:
|
||||||
logging.info(f"[GENERAL] User {ctx.user.id} playlist '{name}' contains explicit content and is not allowed on this server")
|
logging.info(f"[GENERAL] User {ctx.user.id} search for '{name}' returned explicit content and is not allowed on this server")
|
||||||
await ctx.respond("❌ Explicit контент запрещён на этом сервере.", delete_after=15, ephemeral=True)
|
view = None
|
||||||
return
|
embed.set_footer(text="Воспроизведение недоступно, так как в плейлисте присутствуют Explicit треки")
|
||||||
|
break
|
||||||
embed = await generate_item_embed(result)
|
|
||||||
view = ListenView(result)
|
|
||||||
else:
|
|
||||||
result = await client.search(name, nocorrect=True)
|
|
||||||
|
|
||||||
if not result:
|
|
||||||
logging.warning(f"Failed to search for '{name}' for user {ctx.user.id}")
|
|
||||||
await ctx.respond("❌ Что-то пошло не так. Повторите попытку позже.", delete_after=15, ephemeral=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
if content_type == 'Трек':
|
|
||||||
content = result.tracks
|
|
||||||
elif content_type == 'Альбом':
|
|
||||||
content = result.albums
|
|
||||||
elif content_type == 'Артист':
|
|
||||||
content = result.artists
|
|
||||||
elif content_type == 'Плейлист':
|
|
||||||
content = result.playlists
|
|
||||||
|
|
||||||
if not content:
|
|
||||||
logging.info(f"[GENERAL] User {ctx.user.id} search for '{name}' returned no results")
|
|
||||||
await ctx.respond("❌ По запросу ничего не найдено.", delete_after=15, ephemeral=True)
|
|
||||||
return
|
|
||||||
content = content.results[0]
|
|
||||||
|
|
||||||
embed = await generate_item_embed(content)
|
|
||||||
view = ListenView(content)
|
|
||||||
|
|
||||||
if isinstance(content, (Track, Album)) and (content.explicit or content.content_warning) and not guild['allow_explicit']:
|
|
||||||
logging.info(f"[GENERAL] User {ctx.user.id} search for '{name}' returned explicit content and is not allowed on this server")
|
|
||||||
await ctx.respond("❌ Explicit контент запрещён на этом сервере.", delete_after=15, ephemeral=True)
|
|
||||||
return
|
|
||||||
elif isinstance(content, Artist):
|
|
||||||
tracks = await content.get_tracks_async()
|
|
||||||
if not tracks:
|
|
||||||
logging.info(f"[GENERAL] User {ctx.user.id} search for '{name}' returned no tracks")
|
|
||||||
await ctx.respond("❌ Треки от этого исполнителя не найдены.", delete_after=15, ephemeral=True)
|
|
||||||
return
|
|
||||||
for track in tracks:
|
|
||||||
if (track.explicit or track.content_warning) and not guild['allow_explicit']:
|
|
||||||
logging.info(f"[GENERAL] User {ctx.user.id} search for '{name}' returned explicit content and is not allowed on this server")
|
|
||||||
view = None
|
|
||||||
embed.set_footer(text="Воспроизведение недоступно, так как у автора присутствуют Explicit треки")
|
|
||||||
break
|
|
||||||
elif isinstance(content, Playlist):
|
|
||||||
tracks = await content.fetch_tracks_async()
|
|
||||||
if not tracks:
|
|
||||||
logging.info(f"[GENERAL] User {ctx.user.id} search for '{name}' returned no tracks")
|
|
||||||
await ctx.respond("❌ Пустой плейлист.", delete_after=15, ephemeral=True)
|
|
||||||
return
|
|
||||||
for track_short in content.tracks:
|
|
||||||
track = cast(Track, track_short.track)
|
|
||||||
if (track.explicit or track.content_warning) and not guild['allow_explicit']:
|
|
||||||
logging.info(f"[GENERAL] User {ctx.user.id} search for '{name}' returned explicit content and is not allowed on this server")
|
|
||||||
view = None
|
|
||||||
embed.set_footer(text="Воспроизведение недоступно, так как в плейлисте присутствуют Explicit треки")
|
|
||||||
break
|
|
||||||
|
|
||||||
logging.info(f"[GENERAL] Successfully generated '{content_type}' message for user {ctx.author.id}")
|
logging.info(f"[GENERAL] Successfully generated '{content_type}' message for user {ctx.author.id}")
|
||||||
await ctx.respond(embed=embed, view=view)
|
await ctx.respond(embed=embed, view=view)
|
||||||
|
|||||||
@@ -276,20 +276,22 @@ async def _generate_playlist_embed(playlist: Playlist) -> Embed:
|
|||||||
duration = playlist.duration_ms
|
duration = playlist.duration_ms
|
||||||
likes_count = playlist.likes_count
|
likes_count = playlist.likes_count
|
||||||
|
|
||||||
color = 0x000
|
|
||||||
cover_url = None
|
cover_url = None
|
||||||
|
|
||||||
if playlist.cover and playlist.cover.uri:
|
if playlist.cover and playlist.cover.uri:
|
||||||
cover_url = f"https://{playlist.cover.uri.replace('%%', '400x400')}"
|
cover_url = f"https://{playlist.cover.uri.replace('%%', '400x400')}"
|
||||||
else:
|
else:
|
||||||
tracks = await playlist.fetch_tracks_async()
|
tracks = await playlist.fetch_tracks_async()
|
||||||
for i in range(len(tracks)):
|
for track_short in tracks:
|
||||||
track = tracks[i].track
|
track = track_short.track
|
||||||
if not track or not track.albums or not track.albums[0].cover_uri:
|
if track and track.albums and track.albums[0].cover_uri:
|
||||||
continue
|
cover_url = f"https://{track.albums[0].cover_uri.replace('%%', '400x400')}"
|
||||||
|
break
|
||||||
|
|
||||||
if cover_url:
|
if cover_url:
|
||||||
color = await _get_average_color_from_url(cover_url)
|
color = await _get_average_color_from_url(cover_url)
|
||||||
|
else:
|
||||||
|
color = 0x000
|
||||||
|
|
||||||
embed = Embed(
|
embed = Embed(
|
||||||
title=title,
|
title=title,
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
from .other import MyPlaylists, QueueView, generate_queue_embed, generate_playlists_embed
|
from .other import QueueView, generate_queue_embed
|
||||||
from .menu import MenuView
|
from .menu import MenuView
|
||||||
from .find import ListenView
|
from .find import ListenView
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'MyPlaylists',
|
|
||||||
'QueueView',
|
'QueueView',
|
||||||
'MenuView',
|
'MenuView',
|
||||||
'ListenView',
|
'ListenView',
|
||||||
'generate_queue_embed',
|
'generate_queue_embed'
|
||||||
'generate_playlists_embed'
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ class MyVibeButton(Button, VoiceExtension):
|
|||||||
else:
|
else:
|
||||||
_id = 'onyourwave'
|
_id = 'onyourwave'
|
||||||
|
|
||||||
await self.send_menu_message(interaction)
|
await self.send_menu_message(interaction, disable=True)
|
||||||
await self.update_vibe(
|
await self.update_vibe(
|
||||||
interaction,
|
interaction,
|
||||||
track_type_map[type(self.item)],
|
track_type_map[type(self.item)],
|
||||||
|
|||||||
@@ -6,19 +6,6 @@ from discord import ApplicationContext, ButtonStyle, Interaction, Embed
|
|||||||
|
|
||||||
from MusicBot.cogs.utils.voice_extension import VoiceExtension
|
from MusicBot.cogs.utils.voice_extension import VoiceExtension
|
||||||
|
|
||||||
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:
|
def generate_queue_embed(page: int, tracks_list: list[dict[str, Any]]) -> Embed:
|
||||||
count = 15 * page
|
count = 15 * page
|
||||||
length = len(tracks_list)
|
length = len(tracks_list)
|
||||||
@@ -36,61 +23,6 @@ def generate_queue_embed(page: int, tracks_list: list[dict[str, Any]]) -> Embed:
|
|||||||
embed.add_field(name=f"{i} - {track['title']} - {duration_m}:{duration_s:02d}", value="", inline=False)
|
embed.add_field(name=f"{i} - {track['title']} - {duration_m}:{duration_s:02d}", value="", inline=False)
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
|
|
||||||
class MPNextButton(Button, VoiceExtension):
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
Button.__init__(self, **kwargs)
|
|
||||||
VoiceExtension.__init__(self, None)
|
|
||||||
|
|
||||||
async def callback(self, interaction: Interaction) -> None:
|
|
||||||
if not interaction.user:
|
|
||||||
return
|
|
||||||
user = await self.users_db.get_user(interaction.user.id)
|
|
||||||
page = user['playlists_page'] + 1
|
|
||||||
await self.users_db.update(interaction.user.id, {'playlists_page': page})
|
|
||||||
embed = generate_playlists_embed(page, user['playlists'])
|
|
||||||
await interaction.edit(embed=embed, view=await MyPlaylists(interaction).init())
|
|
||||||
|
|
||||||
class MPPrevButton(Button, VoiceExtension):
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
Button.__init__(self, **kwargs)
|
|
||||||
VoiceExtension.__init__(self, None)
|
|
||||||
|
|
||||||
async def callback(self, interaction: Interaction) -> None:
|
|
||||||
if not interaction.user:
|
|
||||||
return
|
|
||||||
user = await self.users_db.get_user(interaction.user.id)
|
|
||||||
page = user['playlists_page'] - 1
|
|
||||||
await self.users_db.update(interaction.user.id, {'playlists_page': page})
|
|
||||||
embed = generate_playlists_embed(page, user['playlists'])
|
|
||||||
await interaction.edit(embed=embed, view=await MyPlaylists(interaction).init())
|
|
||||||
|
|
||||||
class MyPlaylists(View, VoiceExtension):
|
|
||||||
def __init__(self, ctx: ApplicationContext | Interaction, *items: Item, timeout: float | None = 360, disable_on_timeout: bool = True):
|
|
||||||
View.__init__(self, *items, timeout=timeout, disable_on_timeout=disable_on_timeout)
|
|
||||||
VoiceExtension.__init__(self, None)
|
|
||||||
|
|
||||||
self.ctx = ctx
|
|
||||||
self.next_button = MPNextButton(style=ButtonStyle.primary, emoji='▶️')
|
|
||||||
self.prev_button = MPPrevButton(style=ButtonStyle.primary, emoji='◀️')
|
|
||||||
|
|
||||||
async def init(self) -> Self:
|
|
||||||
if not self.ctx.user:
|
|
||||||
return self
|
|
||||||
|
|
||||||
user = await self.users_db.get_user(self.ctx.user.id)
|
|
||||||
count = 10 * user['playlists_page']
|
|
||||||
|
|
||||||
if not user['playlists'][count + 10:]:
|
|
||||||
self.next_button.disabled = True
|
|
||||||
if not user['playlists'][:count]:
|
|
||||||
self.prev_button.disabled = True
|
|
||||||
|
|
||||||
self.add_item(self.prev_button)
|
|
||||||
self.add_item(self.next_button)
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
class QueueNextButton(Button, VoiceExtension):
|
class QueueNextButton(Button, VoiceExtension):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
Button.__init__(self, **kwargs)
|
Button.__init__(self, **kwargs)
|
||||||
|
|||||||
Reference in New Issue
Block a user