@@ -12,17 +12,18 @@ from yandex_music import Track, Album, Artist, Playlist
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
users_db = BaseUsersDatabase ( )
def setup ( bot ) :
bot . add_cog ( General ( bot ) )
async def get_search_suggestions ( ctx : discord . AutocompleteContext ) - > list [ str ] :
if not ctx . interaction . user or not ctx . value or len ( ctx . value ) < 2 :
return [ ]
users_db = BaseUsersDatabase ( )
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 " )
@@ -64,12 +65,30 @@ async def get_search_suggestions(ctx: discord.AutocompleteContext) -> list[str]:
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 ) :
def __init__ ( self , bot : discord . Bot ) :
self . bot = bot
self . db = BaseGuildsDatabase ( )
self . users_db = BaseUsersDatabase ( )
self . users_db = users_db
account = discord . SlashCommandGroup ( " account " , " Команды, связанные с аккаунтом. " )
@@ -106,7 +125,6 @@ class General(Cog):
value = """ `account`
`find`
`help`
`like`
`queue`
`settings`
`track`
@@ -116,26 +134,23 @@ class General(Cog):
embed . set_footer ( text = ' ©️ Bananchiki ' )
elif command == ' account ' :
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 "
" Удалить токен из базы данных бота. \n ```/account remove``` \n "
" Получить ваши плейлисты. Чтобы добавить плейлист в очередь, используйте команду /find . \n ```/account playlists ``` \n "
" Получить ваш плейлист. \n ```/account playlist <название> ``` \n "
" Получить плейлист «Мне нравится». \n ```/account likes``` \n "
" Получить ваши рекомендации. \n ```/account recommendations <тип>``` \n "
)
elif command == ' find ' :
embed . description + = (
" Вывести информацию о треке (по умолчанию), альбоме, авторе или плейлисте. Позволяет добавить музыку в очередь. "
" В названии можно уточнить автора или версию . Возвращается лучшее совпадение.\n ```/find <название> <тип> ``` "
" В названии можно уточнить автора через «-» . Возвращается лучшее совпадение.\n ```/find <тип> < название>``` "
)
elif command == ' help ' :
embed . description + = (
" Вывести список всех команд. \n ```/help``` \n "
" Получить информацию о конкретной команде. \n ```/help <команда>``` "
)
elif command == ' like ' :
embed . description + = (
" Добавить трек в плейлист «Мне нравится». Пользовательские треки из этого плейлиста игнорируются. \n ```/like``` "
)
elif command == ' queue ' :
embed . description + = (
" Получить очередь треков. По 15 элементов на страницу. \n ```/queue get``` \n "
@@ -147,7 +162,8 @@ class General(Cog):
" Получить текущие настройки. \n ```/settings show``` \n "
" Разрешить или запретить воспроизведение Explicit треков и альбомов. Если автор или плейлист содержат Explicit треки, убираются кнопки для доступа к ним. \n ```/settings explicit``` \n "
" Разрешить или запретить создание меню проигрывателя, когда в канале больше одного человека. \n ```/settings menu``` \n "
" Разрешить или запретить голосование. \n ```/settings vote <тип голосования >``` \n "
" Разрешить или запретить голосование. \n ```/settings vote <тип>``` \n "
" Разрешить или запретить отключение/подключение бота к каналу участникам без прав управления каналом. \n ```/settings connect``` \n "
" `Примечание`: Только пользователи с разрешением управления каналом могут менять настройки. "
)
elif command == ' track ' :
@@ -157,6 +173,7 @@ class General(Cog):
" Приостановить текущий трек. \n ```/track pause``` \n "
" Возобновить текущий трек. \n ```/track resume``` \n "
" Прервать проигрывание, удалить историю, очередь и текущий плеер. \n ```/track stop``` \n "
" Добавить трек в плейлист «Мне нравится» или удалить е г о , если он уже там. \n ```/track like``` "
" Запустить Мою Волну по текущему треку. \n ```/track vibe``` "
)
elif command == ' voice ' :
@@ -282,27 +299,57 @@ class General(Cog):
await ctx . respond ( embed = embed , view = view )
@account.command ( description = " Получить ваши плейлисты . " )
async def playlists ( self , ctx : discord . ApplicationContext ) - > None :
@account.command ( description = " Получить ваш плейлист. " )
@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 } " )
guild = await self . db . get_guild ( ctx . guild_id , projection = { ' allow_explicit ' : 1 } )
token = await self . users_db . get_ym_token ( ctx . user . id )
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 )
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 : list [ tuple [ str , int ] ] = [
( playlist . title if playlist . title else ' Без названия ' , playlist . track_count if playlist . track_count else 0 ) for playlist in playlists_list
]
playlists = await client . users_playlists_list ( )
await self . users_db . update ( ctx . user . id , { ' playlists ' : playlists , ' playlists_page ' : 0 } )
embed = generate_playlists_embed ( 0 , playlists )
playlist = next ( ( playlist for playlist in playlists if playlist . title == name ) , None )
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 } " )
await ctx . respond ( embed = embed , view = await MyPlaylists ( ctx ) . init ( ) , ephemeral = True )
tracks = await playlist . fetch_tracks_async ( )
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.option (
@@ -310,7 +357,7 @@ class General(Cog):
parameter_name = ' content_type ' ,
description = " Тип контента для поиска. " ,
type = discord . SlashCommandOptionType . string ,
choices = [ ' Трек ' , ' Альбом ' , ' Артист ' , ' Плейлист ' , ' Свой плейлист ' ],
choices = [ ' Трек ' , ' Альбом ' , ' Артист ' , ' Плейлист ' ] ,
)
@discord.option (
" запрос " ,
@@ -322,11 +369,10 @@ class General(Cog):
async def find (
self ,
ctx : discord . ApplicationContext ,
content_type : Literal [ ' Трек ' , ' Альбом ' , ' Артист ' , ' Плейлист ' , ' Свой плейлист ' ],
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 } ' " )
@@ -344,85 +390,60 @@ class General(Cog):
await ctx . respond ( " ❌ Недействительный токен. Если это не так, попробуйте ещё раз. " , delete_after = 15 , ephemeral = True )
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 ( )
resul t = 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 " )
await ctx . respond ( " ❌ Плейлист не найден. " , delete_after = 15 , ephemeral = True )
return
tracks = await result . fetch_tracks_async ( )
if content_type == ' Трек ' :
conten t = 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 } playlist ' { name } ' is empty " )
await ctx . respond ( " ❌ Плейлист пуст . " , delete_after = 15 , ephemeral = True )
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 tracks :
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 } playlist ' { name } ' contains explicit content and is not allowed on this server " )
await ctx . respond ( " ❌ Explicit контент запрещён на этом сервере. " , delete_after = 15 , ephemeral = True )
return
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] 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 } " )
await ctx . respond ( embed = embed , view = view )