mirror of
https://github.com/deadcxap/YandexMusicDiscordBot.git
synced 2026-01-09 23:51:45 +03:00
feat: Add account recommendations.
This commit is contained in:
@@ -173,7 +173,7 @@ class General(Cog):
|
||||
|
||||
await ctx.respond(response_message, embed=embed, ephemeral=True)
|
||||
|
||||
@account.command(description="Ввести токен от Яндекс Музыки.")
|
||||
@account.command(description="Ввести токен Яндекс Музыки.")
|
||||
@discord.option("token", type=discord.SlashCommandOptionType.string, description="Токен.")
|
||||
async def login(self, ctx: discord.ApplicationContext, token: str) -> None:
|
||||
logging.info(f"[GENERAL] Login command invoked by user {ctx.author.id} in guild {ctx.guild.id}")
|
||||
@@ -190,7 +190,7 @@ class General(Cog):
|
||||
logging.info(f"[GENERAL] Token saved for user {ctx.author.id}")
|
||||
await ctx.respond(f'Привет, {about['account']['first_name']}!', delete_after=15, ephemeral=True)
|
||||
|
||||
@account.command(description="Удалить токен из датабазы бота.")
|
||||
@account.command(description="Удалить токен из базы данных бота.")
|
||||
async def remove(self, ctx: discord.ApplicationContext) -> None:
|
||||
logging.info(f"[GENERAL] Remove command invoked by user {ctx.author.id} in guild {ctx.guild.id}")
|
||||
await self.users_db.update(ctx.user.id, {'ym_token': None})
|
||||
@@ -228,6 +228,60 @@ class General(Cog):
|
||||
logging.info(f"[GENERAL] Successfully fetched likes for user {ctx.user.id}")
|
||||
await ctx.respond(embed=embed, view=ListenView(tracks))
|
||||
|
||||
@account.command(description="Получить ваши рекомендации.")
|
||||
@discord.option(
|
||||
'тип',
|
||||
parameter_name='content_type',
|
||||
description="Вид рекомендаций.",
|
||||
type=discord.SlashCommandOptionType.string,
|
||||
choices=['Премьера', 'Плейлист дня', 'Дежавю']
|
||||
)
|
||||
async def recommendations(
|
||||
self,
|
||||
ctx: discord.ApplicationContext,
|
||||
content_type: Literal['Премьера', 'Плейлист дня', 'Дежавю']
|
||||
)-> None:
|
||||
# NOTE: Recommendations can be accessed by using /find, but it's more convenient to have it in separate command.
|
||||
logging.debug(f"[GENERAL] Recommendations command invoked by user {ctx.user.id} in guild {ctx.guild_id} for type '{content_type}'")
|
||||
|
||||
guild = await self.db.get_guild(ctx.guild_id)
|
||||
token = await self.users_db.get_ym_token(ctx.user.id)
|
||||
if not token:
|
||||
await ctx.respond("❌ Укажите токен через /account login.", delete_after=15, ephemeral=True)
|
||||
return
|
||||
|
||||
client = await YMClient(token).init()
|
||||
|
||||
search = await client.search(content_type, False, 'playlist')
|
||||
if not search or not search.playlists:
|
||||
logging.info(f"[GENERAL] Failed to fetch recommendations for user {ctx.user.id}")
|
||||
await ctx.respond('❌ Что-то пошло не так. Повторите попытку позже.', delete_after=15, ephemeral=True)
|
||||
return
|
||||
|
||||
playlist = search.playlists.results[0]
|
||||
if playlist is None:
|
||||
logging.info(f"[GENERAL] Failed to fetch recommendations for user {ctx.user.id}")
|
||||
await ctx.respond('❌ Что-то пошло не так. Повторите попытку позже.', delete_after=15, ephemeral=True)
|
||||
|
||||
tracks = await playlist.fetch_tracks_async()
|
||||
if not tracks:
|
||||
logging.info(f"[GENERAL] User {ctx.user.id} search for '{content_type}' returned no tracks")
|
||||
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 '{content_type}' 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)
|
||||
|
||||
@account.command(description="Получить ваши плейлисты.")
|
||||
async def playlists(self, ctx: discord.ApplicationContext) -> None:
|
||||
logging.info(f"[GENERAL] Playlists command invoked by user {ctx.user.id} in guild {ctx.guild_id}")
|
||||
@@ -238,11 +292,8 @@ class General(Cog):
|
||||
return
|
||||
|
||||
client = await YMClient(token).init()
|
||||
if not client.me or not client.me.account or not client.me.account.uid:
|
||||
await ctx.respond('❌ Что-то пошло не так. Повторите попытку позже.', delete_after=15, ephemeral=True)
|
||||
return
|
||||
|
||||
playlists_list = await client.users_playlists_list(client.me.account.uid)
|
||||
playlists_list = 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
|
||||
]
|
||||
@@ -274,6 +325,9 @@ class General(Cog):
|
||||
content_type: Literal['Трек', 'Альбом', 'Артист', 'Плейлист', 'Свой плейлист'],
|
||||
name: str
|
||||
) -> None:
|
||||
# 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}'")
|
||||
|
||||
guild = await self.db.get_guild(ctx.guild_id, projection={'allow_explicit': 1})
|
||||
@@ -291,12 +345,8 @@ class General(Cog):
|
||||
return
|
||||
|
||||
if content_type == 'Свой плейлист':
|
||||
if not client.me or not client.me.account or not client.me.account.uid:
|
||||
logging.warning(f"Failed to get user info for user {ctx.user.id}")
|
||||
await ctx.respond("❌ Не удалось получить информацию о пользователе.", delete_after=15, ephemeral=True)
|
||||
return
|
||||
|
||||
playlists = await client.users_playlists_list(client.me.account.uid)
|
||||
playlists = await client.users_playlists_list()
|
||||
result = next((playlist for playlist in playlists if playlist.title == name), None)
|
||||
if not result:
|
||||
logging.info(f"[GENERAL] User {ctx.user.id} playlist '{name}' not found")
|
||||
@@ -371,7 +421,7 @@ class General(Cog):
|
||||
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 треки")
|
||||
embed.set_footer(text="Воспроизведение недоступно, так как в плейлисте присутствуют Explicit треки")
|
||||
break
|
||||
|
||||
logging.info(f"[GENERAL] Successfully generated '{content_type}' message for user {ctx.author.id}")
|
||||
|
||||
@@ -99,10 +99,13 @@ async def _generate_track_embed(track: Track) -> Embed:
|
||||
|
||||
artist_url = f"https://music.yandex.ru/artist/{artist.id}"
|
||||
artist_cover = artist.cover
|
||||
if not artist_cover:
|
||||
|
||||
if not artist_cover and artist.op_image:
|
||||
artist_cover_url = artist.get_op_image_url()
|
||||
else:
|
||||
elif artist_cover:
|
||||
artist_cover_url = artist_cover.get_url()
|
||||
else:
|
||||
artist_cover_url = None
|
||||
|
||||
embed = Embed(
|
||||
title=title,
|
||||
@@ -172,10 +175,13 @@ async def _generate_album_embed(album: Album) -> Embed:
|
||||
|
||||
artist_url = f"https://music.yandex.ru/artist/{artist.id}"
|
||||
artist_cover = artist.cover
|
||||
if not artist_cover:
|
||||
|
||||
if not artist_cover and artist.op_image:
|
||||
artist_cover_url = artist.get_op_image_url()
|
||||
else:
|
||||
elif artist_cover:
|
||||
artist_cover_url = artist_cover.get_url()
|
||||
else:
|
||||
artist_cover_url = None
|
||||
|
||||
embed = Embed(
|
||||
title=title,
|
||||
@@ -264,7 +270,7 @@ async def _generate_playlist_embed(playlist: Playlist) -> Embed:
|
||||
title = cast(str, playlist.title)
|
||||
track_count = playlist.track_count
|
||||
avail = cast(bool, playlist.available)
|
||||
description = playlist.description_formatted
|
||||
description = playlist.description
|
||||
year = playlist.created
|
||||
modified = playlist.modified
|
||||
duration = playlist.duration_ms
|
||||
|
||||
@@ -155,15 +155,31 @@ class MyVibeButton(Button, VoiceExtension):
|
||||
|
||||
track_type_map: dict[Any, Literal['track', 'album', 'artist', 'playlist', 'user']] = {
|
||||
Track: 'track', Album: 'album', Artist: 'artist', Playlist: 'playlist', list: 'user'
|
||||
} # NOTE: Likes playlist should have its own entry instead of 'user:onyourwave'
|
||||
}
|
||||
|
||||
if isinstance(self.item, Playlist):
|
||||
if not self.item.owner:
|
||||
logging.warning(f"[VIBE] Playlist owner is None")
|
||||
await interaction.respond("❌ Не удалось получить информацию о плейлисте.", ephemeral=True)
|
||||
return
|
||||
|
||||
_id = self.item.owner.login + '_' + str(self.item.kind)
|
||||
elif not isinstance(self.item, list):
|
||||
_id = cast(int | str, self.item.id)
|
||||
else:
|
||||
_id = 'onyourwave'
|
||||
|
||||
await self.send_menu_message(interaction)
|
||||
await self.update_vibe(
|
||||
interaction,
|
||||
track_type_map[type(self.item)],
|
||||
cast(int, self.item.uid) if isinstance(self.item, Playlist) else cast(int | str, self.item.id) if not isinstance(self.item, list) else 'onyourwave'
|
||||
_id
|
||||
)
|
||||
|
||||
next_track = await self.db.get_track(gid, 'next')
|
||||
if next_track:
|
||||
await self._play_next_track(interaction, next_track)
|
||||
|
||||
class ListenView(View):
|
||||
def __init__(self, item: Track | Album | Artist | Playlist | list[Track], *items: Item, timeout: float | None = 360, disable_on_timeout: bool = True):
|
||||
super().__init__(*items, timeout=timeout, disable_on_timeout=disable_on_timeout)
|
||||
|
||||
Reference in New Issue
Block a user