impr: Code organization and bug fixes.

This commit is contained in:
Lemon4ksan
2025-02-06 23:05:02 +03:00
parent f7ad7556d1
commit 3cb971b473
8 changed files with 786 additions and 581 deletions

View File

@@ -62,7 +62,7 @@ async def get_search_suggestions(ctx: discord.AutocompleteContext) -> list[str]:
playlists_list = await client.users_playlists_list(client.me.account.uid) playlists_list = await client.users_playlists_list(client.me.account.uid)
res = [playlist.title if playlist.title else 'Без названия' for playlist in playlists_list] res = [playlist.title if playlist.title else 'Без названия' for playlist in playlists_list]
return res return res[:100]
class General(Cog): class General(Cog):

View File

@@ -20,10 +20,12 @@ class Settings(Cog):
@settings.command(name="show", description="Показать текущие настройки бота.") @settings.command(name="show", description="Показать текущие настройки бота.")
async def show(self, ctx: discord.ApplicationContext) -> None: async def show(self, ctx: discord.ApplicationContext) -> None:
guild = await self.db.get_guild(ctx.guild.id, projection={ guild = await self.db.get_guild(ctx.guild.id, projection={
'allow_explicit': 1, 'always_allow_menu': 1, 'vote_next_track': 1, 'vote_add_track': 1, 'vote_add_album': 1, 'vote_add_artist': 1, 'vote_add_playlist': 1 'allow_explicit': 1, 'always_allow_menu': 1,
'vote_next_track': 1, 'vote_add_track': 1, 'vote_add_album': 1, 'vote_add_artist': 1, 'vote_add_playlist': 1,
'allow_connect': 1, 'allow_disconnect': 1
}) })
embed = discord.Embed(title="Настройки бота", color=0xfed42b) embed = discord.Embed(title="Настройки бота", color=0xfed42b)
explicit = "✅ - Разрешены" if guild['allow_explicit'] else "❌ - Запрещены" explicit = "✅ - Разрешены" if guild['allow_explicit'] else "❌ - Запрещены"
menu = "✅ - Всегда доступно" if guild['always_allow_menu'] else "❌ - Если в канале 1 человек." menu = "✅ - Всегда доступно" if guild['always_allow_menu'] else "❌ - Если в канале 1 человек."
@@ -32,14 +34,28 @@ class Settings(Cog):
vote += "\n✅ - Добавление альбомов" if guild['vote_add_album'] else "\n❌ - Добавление альбомов" vote += "\n✅ - Добавление альбомов" if guild['vote_add_album'] else "\n❌ - Добавление альбомов"
vote += "\n✅ - Добавление артистов" if guild['vote_add_artist'] else "\n❌ - Добавление артистов" vote += "\n✅ - Добавление артистов" if guild['vote_add_artist'] else "\n❌ - Добавление артистов"
vote += "\n✅ - Добавление плейлистов" if guild['vote_add_playlist'] else "\n❌ - Добавление плейлистов" vote += "\n✅ - Добавление плейлистов" if guild['vote_add_playlist'] else "\n❌ - Добавление плейлистов"
connect = "\n✅ - Разрешено всем" if guild['allow_connect'] else "\n❌ - Только для участникам с правами управления каналом"
embed.add_field(name="__Explicit треки__", value=explicit, inline=False) embed.add_field(name="__Explicit треки__", value=explicit, inline=False)
embed.add_field(name="__Меню проигрывателя__", value=menu, inline=False) embed.add_field(name="__Меню проигрывателя__", value=menu, inline=False)
embed.add_field(name="__Голосование__", value=vote, inline=False) embed.add_field(name="__Голосование__", value=vote, inline=False)
embed.add_field(name="__Подключение и Отключение__", value=connect, inline=False)
await ctx.respond(embed=embed, ephemeral=True) await ctx.respond(embed=embed, ephemeral=True)
@settings.command(name="explicit", description="Разрешить или запретить воспроизведение Explicit треков.") @settings.command(name="connect", description="Разрешить/запретить отключение/подключение бота к каналу участникам без прав управления каналом.")
async def connect(self, ctx: discord.ApplicationContext) -> None:
member = cast(discord.Member, ctx.author)
if not member.guild_permissions.manage_channels:
await ctx.respond("У вас нет прав для выполнения этой команды.", delete_after=15, ephemeral=True)
return
guild = await self.db.get_guild(ctx.guild.id, projection={'allow_connect': 1})
await self.db.update(ctx.guild.id, {'allow_connect': not guild['allow_connect']})
await ctx.respond(f"Отключение/подключение бота к каналу теперь {'✅ разрешено' if not guild['allow_connect'] else '❌ запрещено'} участникам без прав управления каналом.", delete_after=15, ephemeral=True)
@settings.command(name="explicit", description="Разрешить или запретить воспроизведение Explicit треков (пока что неполноценно).")
async def explicit(self, ctx: discord.ApplicationContext) -> None: async def explicit(self, ctx: discord.ApplicationContext) -> None:
member = cast(discord.Member, ctx.author) member = cast(discord.Member, ctx.author)
if not member.guild_permissions.manage_channels: if not member.guild_permissions.manage_channels:
@@ -48,9 +64,9 @@ class Settings(Cog):
guild = await self.db.get_guild(ctx.guild.id, projection={'allow_explicit': 1}) guild = await self.db.get_guild(ctx.guild.id, projection={'allow_explicit': 1})
await self.db.update(ctx.guild.id, {'allow_explicit': not guild['allow_explicit']}) await self.db.update(ctx.guild.id, {'allow_explicit': not guild['allow_explicit']})
await ctx.respond(f"Треки с содержанием не для детей теперь {'разрешены' if not guild['allow_explicit'] else 'запрещены'}.", delete_after=15, ephemeral=True) await ctx.respond(f"Треки с содержанием не для детей теперь {'разрешены' if not guild['allow_explicit'] else 'запрещены'}.", delete_after=15, ephemeral=True)
@settings.command(name="menu", description="Разрешить или запретить создание меню проигрывателя, даже если в канале больше одного человека.") @settings.command(name="menu", description="Разрешить или запретить использование меню проигрывателя, если в канале больше одного человека.")
async def menu(self, ctx: discord.ApplicationContext) -> None: async def menu(self, ctx: discord.ApplicationContext) -> None:
member = cast(discord.Member, ctx.author) member = cast(discord.Member, ctx.author)
if not member.guild_permissions.manage_channels: if not member.guild_permissions.manage_channels:
@@ -59,7 +75,7 @@ class Settings(Cog):
guild = await self.db.get_guild(ctx.guild.id, projection={'always_allow_menu': 1}) guild = await self.db.get_guild(ctx.guild.id, projection={'always_allow_menu': 1})
await self.db.update(ctx.guild.id, {'always_allow_menu': not guild['always_allow_menu']}) await self.db.update(ctx.guild.id, {'always_allow_menu': not guild['always_allow_menu']})
await ctx.respond(f"Меню проигрывателя теперь {'можно' if not guild['always_allow_menu'] else 'нельзя'} создавать в каналах с несколькими людьми.", delete_after=15, ephemeral=True) await ctx.respond(f"Меню проигрывателя теперь {'✅ доступно' if not guild['always_allow_menu'] else '❌ недоступно'} в каналах с несколькими людьми.", delete_after=15, ephemeral=True)
@settings.command(name="vote", description="Настроить голосование.") @settings.command(name="vote", description="Настроить голосование.")
@discord.option( @discord.option(
@@ -86,7 +102,7 @@ class Settings(Cog):
'vote_add_playlist': False 'vote_add_playlist': False
} }
) )
response_message = "Голосование выключено." response_message = "Голосование выключено."
elif vote_type == '+Всё': elif vote_type == '+Всё':
await self.db.update(ctx.guild.id, { await self.db.update(ctx.guild.id, {
'vote_next_track': True, 'vote_next_track': True,
@@ -96,21 +112,21 @@ class Settings(Cog):
'vote_add_playlist': True 'vote_add_playlist': True
} }
) )
response_message = "Голосование включено." response_message = "Голосование включено."
elif vote_type == 'Переключение': elif vote_type == 'Переключение':
await self.db.update(ctx.guild.id, {'vote_next_track': not guild['vote_next_track']}) await self.db.update(ctx.guild.id, {'vote_next_track': not guild['vote_next_track']})
response_message = "Голосование за переключение трека " + ("выключено." if guild['vote_next_track'] else "включено.") response_message = "Голосование за переключение трека " + ("выключено." if guild['vote_next_track'] else "включено.")
elif vote_type == 'Трек': elif vote_type == 'Трек':
await self.db.update(ctx.guild.id, {'vote_add_track': not guild['vote_add_track']}) await self.db.update(ctx.guild.id, {'vote_add_track': not guild['vote_add_track']})
response_message = "Голосование за добавление трека " + ("выключено." if guild['vote_add_track'] else "включено.") response_message = "Голосование за добавление трека " + ("выключено." if guild['vote_add_track'] else "включено.")
elif vote_type == 'Альбом': elif vote_type == 'Альбом':
await self.db.update(ctx.guild.id, {'vote_add_album': not guild['vote_add_album']}) await self.db.update(ctx.guild.id, {'vote_add_album': not guild['vote_add_album']})
response_message = "Голосование за добавление альбома " + ("выключено." if guild['vote_add_album'] else "включено.") response_message = "Голосование за добавление альбома " + ("выключено." if guild['vote_add_album'] else "включено.")
elif vote_type == 'Артист': elif vote_type == 'Артист':
await self.db.update(ctx.guild.id, {'vote_add_artist': not guild['vote_add_artist']}) await self.db.update(ctx.guild.id, {'vote_add_artist': not guild['vote_add_artist']})
response_message = "Голосование за добавление артиста " + ("выключено." if guild['vote_add_artist'] else "включено.") response_message = "Голосование за добавление артиста " + ("выключено." if guild['vote_add_artist'] else "включено.")
elif vote_type == 'Плейлист': elif vote_type == 'Плейлист':
await self.db.update(ctx.guild.id, {'vote_add_playlist': not guild['vote_add_playlist']}) await self.db.update(ctx.guild.id, {'vote_add_playlist': not guild['vote_add_playlist']})
response_message = "Голосование за добавление плейлиста " + ("выключено." if guild['vote_add_playlist'] else "включено.") response_message = "Голосование за добавление плейлиста " + ("выключено." if guild['vote_add_playlist'] else "включено.")
await ctx.respond(response_message, delete_after=15, ephemeral=True) await ctx.respond(response_message, delete_after=15, ephemeral=True)

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,7 @@ class Voice(Cog, VoiceExtension):
def __init__(self, bot: discord.Bot): def __init__(self, bot: discord.Bot):
VoiceExtension.__init__(self, bot) VoiceExtension.__init__(self, bot)
self.typed_bot: discord.Bot = bot # should be removed later self.typed_bot: discord.Bot = bot
@Cog.listener() @Cog.listener()
async def on_voice_state_update(self, member: discord.Member, before: discord.VoiceState, after: discord.VoiceState) -> None: async def on_voice_state_update(self, member: discord.Member, before: discord.VoiceState, after: discord.VoiceState) -> None:
@@ -38,15 +38,21 @@ class Voice(Cog, VoiceExtension):
if len(channel.members) == 1 and vc: if len(channel.members) == 1 and vc:
logging.info(f"[VOICE] Clearing history and stopping playback for guild {gid}") logging.info(f"[VOICE] Clearing history and stopping playback for guild {gid}")
if member.guild.id in menu_views:
menu_views[member.guild.id].stop()
del menu_views[member.guild.id]
if guild['current_menu']: if guild['current_menu']:
message = self.typed_bot.get_message(guild['current_menu']) message = self.typed_bot.get_message(guild['current_menu'])
if message: if message:
await message.delete() await message.delete()
if member.guild.id in menu_views:
menu_views[member.guild.id].stop()
del menu_views[member.guild.id]
await self.db.update(gid, {'previous_tracks': [], 'next_tracks': [], 'vibing': False, 'current_menu': None, 'repeat': False, 'shuffle': False}) await self.db.update(gid, {
'previous_tracks': [], 'next_tracks': [], 'votes': [],
'current_track': None, 'current_menu': None, 'vibing': False,
'repeat': False, 'shuffle': False, 'is_stopped': True
})
vc.stop() vc.stop()
elif len(channel.members) > 2 and not guild['always_allow_menu']: elif len(channel.members) > 2 and not guild['always_allow_menu']:
if current_menu: if current_menu:
@@ -90,14 +96,17 @@ class Voice(Cog, VoiceExtension):
guild_id = payload.guild_id guild_id = payload.guild_id
if not guild_id: if not guild_id:
return return
guild = await self.db.get_guild(guild_id, projection={'votes': 1, 'current_track': 1}) guild = await self.db.get_guild(guild_id, projection={'votes': 1, 'current_track': 1})
votes = guild['votes'] votes = guild['votes']
if payload.message_id not in votes: if str(payload.message_id) not in votes:
logging.info(f"[VOICE] Message {payload.message_id} not found in votes") logging.info(f"[VOICE] Message {payload.message_id} not found in votes")
return return
vote_data = votes[str(payload.message_id)] vote_data = votes[str(payload.message_id)]
logging.debug(f"[VOICE] Vote data for message {payload.message_id}: {vote_data}")
if payload.emoji.name == '': if payload.emoji.name == '':
logging.info(f"[VOICE] User {payload.user_id} voted positively for message {payload.message_id}") logging.info(f"[VOICE] User {payload.user_id} voted positively for message {payload.message_id}")
vote_data['positive_votes'].append(payload.user_id) vote_data['positive_votes'].append(payload.user_id)
@@ -113,7 +122,6 @@ class Voice(Cog, VoiceExtension):
if vote_data['action'] == 'next': if vote_data['action'] == 'next':
logging.info(f"[VOICE] Skipping track for message {payload.message_id}") logging.info(f"[VOICE] Skipping track for message {payload.message_id}")
await self.db.update(guild_id, {'is_stopped': False})
title = await self.next_track(payload) title = await self.next_track(payload)
await message.clear_reactions() await message.clear_reactions()
await message.edit(content=f"Сейчас играет: **{title}**!", delete_after=15) await message.edit(content=f"Сейчас играет: **{title}**!", delete_after=15)
@@ -128,7 +136,6 @@ class Voice(Cog, VoiceExtension):
logging.info(f"[VOICE] Recieved empty vote context for message {payload.message_id}") logging.info(f"[VOICE] Recieved empty vote context for message {payload.message_id}")
return return
await self.db.update(guild_id, {'is_stopped': False})
await self.db.modify_track(guild_id, track, 'next', 'append') await self.db.modify_track(guild_id, track, 'next', 'append')
if guild['current_track']: if guild['current_track']:
@@ -179,6 +186,10 @@ class Voice(Cog, VoiceExtension):
return return
guild = await self.db.get_guild(guild_id, projection={'votes': 1}) guild = await self.db.get_guild(guild_id, projection={'votes': 1})
votes = guild['votes'] votes = guild['votes']
if str(payload.message_id) not in votes:
logging.info(f"[VOICE] Message {payload.message_id} not found in votes")
return
channel = cast(discord.VoiceChannel, self.typed_bot.get_channel(payload.channel_id)) channel = cast(discord.VoiceChannel, self.typed_bot.get_channel(payload.channel_id))
if not channel: if not channel:
@@ -219,7 +230,9 @@ class Voice(Cog, VoiceExtension):
logging.info(f"[VOICE] Join command invoked by user {ctx.author.id} in guild {ctx.guild.id}") logging.info(f"[VOICE] Join command invoked by user {ctx.author.id} in guild {ctx.guild.id}")
member = cast(discord.Member, ctx.author) member = cast(discord.Member, ctx.author)
if not member.guild_permissions.manage_channels: guild = await self.db.get_guild(ctx.guild.id, projection={'allow_connect': 1})
if not member.guild_permissions.manage_channels and not guild['allow_connect']:
response_message = "У вас нет прав для выполнения этой команды." response_message = "У вас нет прав для выполнения этой команды."
elif (vc := await self.get_voice_client(ctx)) and vc.is_connected(): elif (vc := await self.get_voice_client(ctx)) and vc.is_connected():
response_message = "❌ Бот уже находится в голосовом канале. Выключите его с помощью команды /voice leave." response_message = "❌ Бот уже находится в голосовом канале. Выключите его с помощью команды /voice leave."
@@ -237,16 +250,21 @@ class Voice(Cog, VoiceExtension):
logging.info(f"[VOICE] Leave command invoked by user {ctx.author.id} in guild {ctx.guild.id}") logging.info(f"[VOICE] Leave command invoked by user {ctx.author.id} in guild {ctx.guild.id}")
member = cast(discord.Member, ctx.author) member = cast(discord.Member, ctx.author)
if not member.guild_permissions.manage_channels: guild = await self.db.get_guild(ctx.guild.id, projection={'allow_connect': 1})
if not member.guild_permissions.manage_channels and not guild['allow_connect']:
logging.info(f"[VOICE] User {ctx.author.id} does not have permissions to execute leave command in guild {ctx.guild.id}") logging.info(f"[VOICE] User {ctx.author.id} does not have permissions to execute leave command in guild {ctx.guild.id}")
await ctx.respond("У вас нет прав для выполнения этой команды.", delete_after=15, ephemeral=True) await ctx.respond("У вас нет прав для выполнения этой команды.", delete_after=15, ephemeral=True)
return return
if (vc := await self.get_voice_client(ctx)) and await self.voice_check(ctx): if (vc := await self.get_voice_client(ctx)) and await self.voice_check(ctx):
await self.stop_playing(ctx, full=True) res = await self.stop_playing(ctx, full=True)
await vc.disconnect(force=True) if res:
await ctx.respond("Отключение успешно!", delete_after=15, ephemeral=True) await vc.disconnect(force=True)
logging.info(f"[VOICE] Successfully disconnected from voice channel in guild {ctx.guild.id}") await ctx.respond("Отключение успешно!", delete_after=15, ephemeral=True)
logging.info(f"[VOICE] Successfully disconnected from voice channel in guild {ctx.guild.id}")
else:
await ctx.respond("Не удалось отключиться.", delete_after=15, ephemeral=True)
@queue.command(description="Очистить очередь треков и историю прослушивания.") @queue.command(description="Очистить очередь треков и историю прослушивания.")
async def clear(self, ctx: discord.ApplicationContext) -> None: async def clear(self, ctx: discord.ApplicationContext) -> None:
@@ -294,7 +312,7 @@ class Voice(Cog, VoiceExtension):
menu = await self.db.get_current_menu(ctx.guild.id) menu = await self.db.get_current_menu(ctx.guild.id)
if menu: if menu:
await self.update_menu_embed(ctx, menu) await self.update_menu_full(ctx, menu)
logging.info(f"[VOICE] Track paused in guild {ctx.guild.id}") logging.info(f"[VOICE] Track paused in guild {ctx.guild.id}")
await ctx.respond("Воспроизведение приостановлено.", delete_after=15, ephemeral=True) await ctx.respond("Воспроизведение приостановлено.", delete_after=15, ephemeral=True)
@@ -318,7 +336,7 @@ class Voice(Cog, VoiceExtension):
vc.resume() vc.resume()
menu = await self.db.get_current_menu(ctx.guild.id) menu = await self.db.get_current_menu(ctx.guild.id)
if menu: if menu:
await self.update_menu_embed(ctx, menu) await self.update_menu_full(ctx, menu)
logging.info(f"[VOICE] Track resumed in guild {ctx.guild.id}") logging.info(f"[VOICE] Track resumed in guild {ctx.guild.id}")
await ctx.respond("Воспроизведение восстановлено.", delete_after=15, ephemeral=True) await ctx.respond("Воспроизведение восстановлено.", delete_after=15, ephemeral=True)
else: else:
@@ -337,9 +355,11 @@ class Voice(Cog, VoiceExtension):
await ctx.respond("❌ Вы не можете остановить воспроизведение, пока в канале находятся другие пользователи.", delete_after=15, ephemeral=True) await ctx.respond("❌ Вы не можете остановить воспроизведение, пока в канале находятся другие пользователи.", delete_after=15, ephemeral=True)
elif await self.voice_check(ctx): elif await self.voice_check(ctx):
await self.stop_playing(ctx, full=True) res = await self.stop_playing(ctx, full=True)
if res:
await ctx.respond("Воспроизведение остановлено.", delete_after=15, ephemeral=True) await ctx.respond("Воспроизведение остановлено.", delete_after=15, ephemeral=True)
else:
await ctx.respond("❌ Произошла ошибка при остановке воспроизведения.", delete_after=15, ephemeral=True)
@track.command(description="Переключиться на следующую песню в очереди.") @track.command(description="Переключиться на следующую песню в очереди.")
async def next(self, ctx: discord.ApplicationContext) -> None: async def next(self, ctx: discord.ApplicationContext) -> None:
@@ -397,16 +417,18 @@ class Voice(Cog, VoiceExtension):
await ctx.respond("Нет воспроизводимого трека.", delete_after=15, ephemeral=True) await ctx.respond("Нет воспроизводимого трека.", delete_after=15, ephemeral=True)
return return
result = await self.like_track(ctx) result = await self.react_track(ctx, 'like')
if not result: if not result[0]:
logging.warning(f"Like command failed for user {ctx.author.id} in guild {ctx.guild.id}") logging.warning(f"Like command failed for user {ctx.author.id} in guild {ctx.guild.id}")
await ctx.respond("❌ Операция не удалась.", delete_after=15, ephemeral=True) await ctx.respond("❌ Операция не удалась.", delete_after=15, ephemeral=True)
elif result == 'TRACK REMOVED': elif result[1] == 'removed':
logging.info(f"[VOICE] Track removed from favorites for user {ctx.author.id} in guild {ctx.guild.id}") logging.info(f"[VOICE] Track removed from favorites for user {ctx.author.id} in guild {ctx.guild.id}")
await ctx.respond("Трек был удалён из избранного.", delete_after=15, ephemeral=True) await ctx.respond("Трек был удалён из избранного.", delete_after=15, ephemeral=True)
else: elif result[1] == 'added':
logging.info(f"[VOICE] Track added to favorites for user {ctx.author.id} in guild {ctx.guild.id}") logging.info(f"[VOICE] Track added to favorites for user {ctx.author.id} in guild {ctx.guild.id}")
await ctx.respond(f"Трек **{result}** был добавлен в избранное.", delete_after=15, ephemeral=True) await ctx.respond(f"Трек **{result}** был добавлен в избранное.", delete_after=15, ephemeral=True)
else:
raise ValueError(f"Unknown like command result: '{result}'")
@track.command(name='vibe', description="Запустить Мою Волну по текущему треку.") @track.command(name='vibe', description="Запустить Мою Волну по текущему треку.")
async def track_vibe(self, ctx: discord.ApplicationContext) -> None: async def track_vibe(self, ctx: discord.ApplicationContext) -> None:
@@ -414,7 +436,7 @@ class Voice(Cog, VoiceExtension):
if not await self.voice_check(ctx): if not await self.voice_check(ctx):
return return
guild = await self.db.get_guild(ctx.guild.id, projection={'always_allow_menu': 1, 'current_track': 1}) guild = await self.db.get_guild(ctx.guild.id, projection={'always_allow_menu': 1, 'current_track': 1, 'current_menu': 1})
channel = cast(discord.VoiceChannel, ctx.channel) channel = cast(discord.VoiceChannel, ctx.channel)
if len(channel.members) > 2 and not guild['always_allow_menu']: if len(channel.members) > 2 and not guild['always_allow_menu']:
@@ -426,10 +448,17 @@ class Voice(Cog, VoiceExtension):
await ctx.respond("❌ Нет воспроизводимого трека.", ephemeral=True) await ctx.respond("❌ Нет воспроизводимого трека.", ephemeral=True)
return return
await self.send_menu_message(ctx)
feedback = await self.update_vibe(ctx, 'track', guild['current_track']['id']) feedback = await self.update_vibe(ctx, 'track', guild['current_track']['id'])
if not feedback: if not feedback:
await ctx.respond("❌ Операция не удалась. Возможно, у вес нет подписки на Яндекс Музыку.", ephemeral=True) await ctx.respond("❌ Операция не удалась. Возможно, у вес нет подписки на Яндекс Музыку.", ephemeral=True)
return
if not guild['current_menu']:
await self.send_menu_message(ctx)
next_track = await self.db.get_track(ctx.guild_id, 'next')
if next_track:
await self._play_next_track(ctx, next_track)
@voice.command(name='vibe', description="Запустить Мою Волну.") @voice.command(name='vibe', description="Запустить Мою Волну.")
async def user_vibe(self, ctx: discord.ApplicationContext) -> None: async def user_vibe(self, ctx: discord.ApplicationContext) -> None:
@@ -437,7 +466,7 @@ class Voice(Cog, VoiceExtension):
if not await self.voice_check(ctx): if not await self.voice_check(ctx):
return return
guild = await self.db.get_guild(ctx.guild.id, projection={'always_allow_menu': 1}) guild = await self.db.get_guild(ctx.guild.id, projection={'always_allow_menu': 1, 'current_menu': 1})
channel = cast(discord.VoiceChannel, ctx.channel) channel = cast(discord.VoiceChannel, ctx.channel)
if len(channel.members) > 2 and not guild['always_allow_menu']: if len(channel.members) > 2 and not guild['always_allow_menu']:
@@ -445,7 +474,14 @@ class Voice(Cog, VoiceExtension):
await ctx.respond("❌ Вы не единственный в голосовом канале.", ephemeral=True) await ctx.respond("❌ Вы не единственный в голосовом канале.", ephemeral=True)
return return
await self.send_menu_message(ctx)
feedback = await self.update_vibe(ctx, 'user', 'onyourwave') feedback = await self.update_vibe(ctx, 'user', 'onyourwave')
if not feedback: if not feedback:
await ctx.respond("❌ Операция не удалась. Возможно, у вес нет подписки на Яндекс Музыку.", ephemeral=True) await ctx.respond("❌ Операция не удалась. Возможно, у вес нет подписки на Яндекс Музыку.", ephemeral=True)
return
if not guild['current_menu']:
await self.send_menu_message(ctx)
next_track = await self.db.get_track(ctx.guild_id, 'next')
if next_track:
await self._play_next_track(ctx, next_track)

View File

@@ -1,5 +1,5 @@
from typing import Iterable, Any, cast from typing import Iterable, Any, cast
from pymongo import AsyncMongoClient, ReturnDocument from pymongo import AsyncMongoClient, ReturnDocument, UpdateOne
from pymongo.asynchronous.collection import AsyncCollection from pymongo.asynchronous.collection import AsyncCollection
from pymongo.results import UpdateResult from pymongo.results import UpdateResult
@@ -43,6 +43,19 @@ class BaseUsersDatabase:
upsert=True, upsert=True,
projection=projection projection=projection
) )
ops = []
for key, value in self.DEFAULT_USER.items():
if key not in user and (projection is None or key in projection):
user[key] = value
ops.append(UpdateOne({'_id': uid}, {'$set': {key: value}}))
for key, value in user.copy().items():
if key not in self.DEFAULT_USER and key != '_id':
del user[key]
ops.append(UpdateOne({'_id': uid}, {'$unset': {key: ''}}))
if ops:
await users.bulk_write(ops)
return cast(ExplicitUser, user) return cast(ExplicitUser, user)
async def get_ym_token(self, uid: int) -> str | None: async def get_ym_token(self, uid: int) -> str | None:
@@ -68,6 +81,7 @@ class BaseGuildsDatabase:
is_stopped=True, is_stopped=True,
allow_explicit=True, allow_explicit=True,
always_allow_menu=False, always_allow_menu=False,
allow_connect=False,
vote_next_track=True, vote_next_track=True,
vote_add_track=True, vote_add_track=True,
vote_add_album=True, vote_add_album=True,
@@ -95,6 +109,19 @@ class BaseGuildsDatabase:
upsert=True, upsert=True,
projection=projection projection=projection
) )
ops = []
for key, value in self.DEFAULT_GUILD.items():
if key not in guild and (projection is None or key in projection):
guild[key] = value
ops.append(UpdateOne({'_id': gid}, {'$set': {key: value}}))
for key, value in guild.copy().items():
if key not in self.DEFAULT_GUILD and key != '_id':
del guild[key]
ops.append(UpdateOne({'_id': gid}, {'$unset': {key: ''}}))
if ops:
await guilds.bulk_write(ops)
return cast(ExplicitGuild, guild) return cast(ExplicitGuild, guild)
async def update_vote(self, gid: int, mid: int, data: MessageVotes) -> UpdateResult: async def update_vote(self, gid: int, mid: int, data: MessageVotes) -> UpdateResult:

View File

@@ -55,7 +55,7 @@ class VoiceGuildsDatabase(BaseGuildsDatabase):
operations = { operations = {
'insert': {'$push': {field: {'$each': track_data, '$position': 0}}}, 'insert': {'$push': {field: {'$each': track_data, '$position': 0}}},
'append': {'$push': {field: {'$each': track_data}}}, 'append': {'$push': {field: {'$each': track_data}}},
'extend': {'$push': {field: {'$each': track_data}}}, 'extend': {'$push': {field: {'$each': track_data}}}, # Same as append for consistency with python
'pop_start': {'$pop': {field: -1}}, 'pop_start': {'$pop': {field: -1}},
'pop_end': {'$pop': {field: 1}} 'pop_end': {'$pop': {field: 1}}
} }
@@ -139,9 +139,7 @@ class VoiceGuildsDatabase(BaseGuildsDatabase):
await guilds.update_one( await guilds.update_one(
{'_id': gid}, {'_id': gid},
{ {'$set': {'current_track': track}}
'$set': {'current_track': track}
}
) )
async def clear_tracks(self, gid: int, list_type: Literal['next', 'previous']) -> None: async def clear_tracks(self, gid: int, list_type: Literal['next', 'previous']) -> None:

View File

@@ -15,6 +15,7 @@ class Guild(TypedDict, total=False):
is_stopped: bool is_stopped: bool
allow_explicit: bool allow_explicit: bool
always_allow_menu: bool always_allow_menu: bool
allow_connect: bool
vote_next_track: bool vote_next_track: bool
vote_add_track: bool vote_add_track: bool
vote_add_album: bool vote_add_album: bool
@@ -35,6 +36,7 @@ class ExplicitGuild(TypedDict):
is_stopped: bool # Prevents the `after` callback of play_track is_stopped: bool # Prevents the `after` callback of play_track
allow_explicit: bool allow_explicit: bool
always_allow_menu: bool always_allow_menu: bool
allow_connect: bool
vote_next_track: bool vote_next_track: bool
vote_add_track: bool vote_add_track: bool
vote_add_album: bool vote_add_album: bool

View File

@@ -5,44 +5,34 @@ from discord.ui import View, Button, Item, Select
from discord import VoiceChannel, ButtonStyle, Interaction, ApplicationContext, RawReactionActionEvent, Embed, ComponentType, SelectOption from discord import VoiceChannel, ButtonStyle, Interaction, ApplicationContext, RawReactionActionEvent, Embed, ComponentType, SelectOption
import yandex_music.exceptions import yandex_music.exceptions
from yandex_music import Track, Playlist, ClientAsync as YMClient from yandex_music import TrackLyrics, Playlist, ClientAsync as YMClient
from MusicBot.cogs.utils.voice_extension import VoiceExtension, menu_views from MusicBot.cogs.utils.voice_extension import VoiceExtension, menu_views
class ToggleRepeatButton(Button, VoiceExtension): class ToggleButton(Button, VoiceExtension):
def __init__(self, **kwargs): def __init__(self, *args, **kwargs):
Button.__init__(self, **kwargs) super().__init__(*args, **kwargs)
VoiceExtension.__init__(self, None) VoiceExtension.__init__(self, None)
async def callback(self, interaction: Interaction) -> None: async def callback(self, interaction: Interaction):
logging.info('[MENU] Repeat button callback...') callback_type = interaction.custom_id
if not await self.voice_check(interaction) or not interaction.guild: if callback_type not in ('repeat', 'shuffle'):
raise ValueError(f"Invalid callback type: '{callback_type}'")
logging.info(f'[MENU] {callback_type.capitalize()} button callback')
if not (gid := interaction.guild_id):
logging.warning('[MENU] Failed to get guild ID.')
await interaction.respond("❌ Что-то пошло не так. Попробуйте снова.", delete_after=15, ephemeral=True)
return return
gid = interaction.guild.id
guild = await self.db.get_guild(gid) if not await self.voice_check(interaction, check_vibe_privilage=True):
await self.db.update(gid, {'repeat': not guild['repeat']})
if gid in menu_views:
menu_views[gid].stop()
menu_views[gid] = await MenuView(interaction).init()
await interaction.edit(view=menu_views[gid])
class ToggleShuffleButton(Button, VoiceExtension):
def __init__(self, **kwargs):
Button.__init__(self, **kwargs)
VoiceExtension.__init__(self, None)
async def callback(self, interaction: Interaction) -> None:
logging.info('[MENU] Shuffle button callback...')
if not await self.voice_check(interaction) or not interaction.guild:
return return
gid = interaction.guild.id
guild = await self.db.get_guild(gid)
await self.db.update(gid, {'shuffle': not guild['shuffle']})
if gid in menu_views: guild = await self.db.get_guild(gid)
menu_views[gid].stop() await self.db.update(gid, {callback_type: not guild[callback_type]})
menu_views[gid] = await MenuView(interaction).init()
await interaction.edit(view=menu_views[gid]) if not await self.update_menu_view(interaction, guild, button_callback=True):
await interaction.respond("❌ Что-то пошло не так. Попробуйте снова.", delete_after=15, ephemeral=True)
class PlayPauseButton(Button, VoiceExtension): class PlayPauseButton(Button, VoiceExtension):
def __init__(self, **kwargs): def __init__(self, **kwargs):
@@ -51,11 +41,10 @@ class PlayPauseButton(Button, VoiceExtension):
async def callback(self, interaction: Interaction) -> None: async def callback(self, interaction: Interaction) -> None:
logging.info('[MENU] Play/Pause button callback...') logging.info('[MENU] Play/Pause button callback...')
if not await self.voice_check(interaction): if not await self.voice_check(interaction, check_vibe_privilage=True):
return return
vc = await self.get_voice_client(interaction) if not (vc := await self.get_voice_client(interaction)) or not interaction.message:
if not vc or not interaction.message:
return return
try: try:
@@ -73,117 +62,95 @@ class PlayPauseButton(Button, VoiceExtension):
await interaction.edit(embed=embed) await interaction.edit(embed=embed)
class NextTrackButton(Button, VoiceExtension): class SwitchTrackButton(Button, VoiceExtension):
def __init__(self, **kwargs): def __init__(self, *args, **kwargs):
Button.__init__(self, **kwargs) super().__init__(*args, **kwargs)
VoiceExtension.__init__(self, None) VoiceExtension.__init__(self, None)
async def callback(self, interaction: Interaction) -> None: async def callback(self, interaction: Interaction):
logging.info('[MENU] Next track button callback...') callback_type = interaction.custom_id
if not await self.voice_check(interaction): if callback_type not in ('next', 'previous'):
raise ValueError(f"Invalid callback type: '{callback_type}'")
logging.info(f'[MENU] {callback_type.capitalize()} track button callback')
if not await self.voice_check(interaction, check_vibe_privilage=True):
return return
title = await self.next_track(interaction, button_callback=True)
if callback_type == 'next':
title = await self.next_track(interaction, button_callback=True)
else:
title = await self.prev_track(interaction, button_callback=True)
if not title: if not title:
await interaction.respond(f"❌ Нет треков в очереди.", delete_after=15, ephemeral=True) await interaction.respond(f"❌ Нет треков в очереди.", delete_after=15, ephemeral=True)
class PrevTrackButton(Button, VoiceExtension): class ReactionButton(Button, VoiceExtension):
def __init__(self, **kwargs): def __init__(self, *args, **kwargs):
Button.__init__(self, **kwargs) super().__init__(*args, **kwargs)
VoiceExtension.__init__(self, None) VoiceExtension.__init__(self, None)
async def callback(self, interaction: Interaction) -> None: async def callback(self, interaction: Interaction):
logging.info('[MENU] Previous track button callback...') callback_type = interaction.custom_id
if not await self.voice_check(interaction): if callback_type not in ('like', 'dislike'):
return raise ValueError(f"Invalid callback type: '{callback_type}'")
title = await self.prev_track(interaction, button_callback=True)
if not title:
await interaction.respond(f"Нет треков в истории.", delete_after=15, ephemeral=True)
class LikeButton(Button, VoiceExtension): logging.info(f'[MENU] {callback_type.capitalize()} button callback')
def __init__(self, **kwargs):
Button.__init__(self, **kwargs)
VoiceExtension.__init__(self, None)
async def callback(self, interaction: Interaction) -> None: if not await self.voice_check(interaction) or not (gid := interaction.guild_id):
logging.info('[MENU] Like button callback...')
if not await self.voice_check(interaction, check_vibe_privilage=False):
return
if not interaction.guild:
return
gid = interaction.guild.id
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)
if gid in menu_views:
menu_views[gid].stop()
menu_views[gid] = await MenuView(interaction).init()
await interaction.edit(view=menu_views[gid])
class DislikeButton(Button, VoiceExtension):
def __init__(self, **kwargs):
Button.__init__(self, **kwargs)
VoiceExtension.__init__(self, None)
async def callback(self, interaction: Interaction) -> None:
logging.info('[MENU] Dislike button callback...')
if not await self.voice_check(interaction):
return return
if not (vc := await self.get_voice_client(interaction)) or not vc.is_playing: if not (vc := await self.get_voice_client(interaction)) or not vc.is_playing:
await interaction.respond("❌ Нет воспроизводимого трека.", delete_after=15, ephemeral=True) await interaction.respond("❌ Нет воспроизводимого трека.", delete_after=15, ephemeral=True)
res = await self.dislike_track(interaction) res = await self.react_track(interaction, callback_type)
if res:
logging.debug("[VC_EXT] Disliked track") if callback_type == 'like' and res[0]:
await self._update_menu_views_dict(interaction)
await interaction.edit(view=menu_views[gid])
elif callback_type == 'dislike' and res[0]:
await self.next_track(interaction, vc=vc, button_callback=True) await self.next_track(interaction, vc=vc, button_callback=True)
else: else:
logging.debug("[VC_EXT] Failed to dislike track") logging.debug(f"[VC_EXT] Failed to {callback_type} track")
await interaction.respond("Не удалось поставить дизлайк. Попробуйте позже.") await interaction.respond("Операция не удалась. Попробуйте позже.")
class LyricsButton(Button, VoiceExtension): class LyricsButton(Button, VoiceExtension):
def __init__(self, **kwargs): def __init__(self, **kwargs):
Button.__init__(self, **kwargs) super().__init__(**kwargs)
VoiceExtension.__init__(self, None) VoiceExtension.__init__(self, None)
async def callback(self, interaction: Interaction) -> None: async def callback(self, interaction: Interaction) -> None:
logging.info('[MENU] Lyrics button callback...') logging.info('[MENU] Lyrics button callback...')
if not await self.voice_check(interaction, check_vibe_privilage=False) or not interaction.guild_id or not interaction.user: if not await self.voice_check(interaction) or not interaction.guild_id or not interaction.user:
return return
ym_token = await self.users_db.get_ym_token(interaction.user.id) client = await self.init_ym_client(interaction)
current_track = await self.db.get_track(interaction.guild_id, 'current') if not client:
if not current_track or not ym_token:
return return
track = cast(Track, Track.de_json( current_track = await self.db.get_track(interaction.guild_id, 'current')
current_track, if not current_track:
YMClient(ym_token), # type: ignore # Async client can be used here logging.debug('[MENU] No current track found')
)) return
try: try:
lyrics = await track.get_lyrics_async() lyrics = cast(TrackLyrics, await client.tracks_lyrics(current_track['id']))
except yandex_music.exceptions.NotFoundError: except yandex_music.exceptions.NotFoundError:
logging.debug('[MENU] Lyrics not found') logging.debug('[MENU] Lyrics not found')
await interaction.respond("❌ Текст песни не найден. Яндекс нам соврал (опять)!", delete_after=15, ephemeral=True) await interaction.respond("❌ Текст песни не найден. Яндекс нам соврал (опять)!", delete_after=15, ephemeral=True)
return return
if not lyrics:
logging.debug('[MENU] Lyrics not found')
return
embed = Embed( embed = Embed(
title=track.title, title=current_track['title'],
description='**Текст песни**', description='**Текст песни**',
color=0xfed42b, color=0xfed42b,
) )
text = await lyrics.fetch_lyrics_async() text = await lyrics.fetch_lyrics_async()
for subtext in text.split('\n\n'): for subtext in text.split('\n\n'):
embed.add_field(name='', value=subtext, inline=False) embed.add_field(name='', value=subtext, inline=False)
await interaction.respond(embed=embed, ephemeral=True) await interaction.respond(embed=embed, ephemeral=True)
class MyVibeButton(Button, VoiceExtension): class MyVibeButton(Button, VoiceExtension):
@@ -192,62 +159,75 @@ class MyVibeButton(Button, VoiceExtension):
VoiceExtension.__init__(self, None) VoiceExtension.__init__(self, None)
async def callback(self, interaction: Interaction) -> None: async def callback(self, interaction: Interaction) -> None:
logging.info('[VIBE] My vibe button callback') logging.info('[MENU] My vibe button callback')
if not await self.voice_check(interaction): if not await self.voice_check(interaction):
return return
if not interaction.guild_id: if not interaction.guild_id:
logging.warning('[VIBE] No guild id in button callback') logging.warning('[MENU] No guild id in button callback')
return return
track = await self.db.get_track(interaction.guild_id, 'current') track = await self.db.get_track(interaction.guild_id, 'current')
if track: if track:
logging.info(f"[MENU] Playing vibe for track '{track["id"]}'") logging.info(f"[MENU] Playing vibe for track '{track["id"]}'")
await self.update_vibe( res = await self.update_vibe(
interaction, interaction,
'track', 'track',
track['id'], track['id']
button_callback=True
) )
else: else:
logging.info('[VIBE] Playing on your wave') logging.info('[MENU] Playing station user:onyourwave')
await self.update_vibe( res = await self.update_vibe(
interaction, interaction,
'user', 'user',
'onyourwave', 'onyourwave'
button_callback=True
) )
if not res:
logging.warning('[MENU] Failed to start the vibe')
await interaction.respond('Не удалось запустить "Мою Волну". Попробуйте позже.', ephemeral=True)
next_track = await self.db.get_track(interaction.guild_id, 'next')
if next_track:
# Need to avoid additional feedback.
# TODO: Make it more elegant
await self._play_next_track(interaction, next_track, button_callback=True)
class MyVibeSelect(Select, VoiceExtension): class MyVibeSelect(Select, VoiceExtension):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
VoiceExtension.__init__(self, None) VoiceExtension.__init__(self, None)
async def callback(self, interaction: Interaction) -> None: async def callback(self, interaction: Interaction) -> None:
logging.info('[VIBE] My vibe select callback') logging.info('[MENU] My vibe select callback')
if not await self.voice_check(interaction):
return
if not interaction.user: if not interaction.user:
logging.warning('[VIBE] No user in select callback') logging.warning('[MENU] No user in select callback')
return return
custom_id = interaction.custom_id custom_id = interaction.custom_id
if custom_id not in ('diversity', 'mood', 'lang'): if custom_id not in ('diversity', 'mood', 'lang'):
logging.warning(f'[VIBE] Unknown custom_id: {custom_id}') logging.warning(f'[MENU] Unknown custom_id: {custom_id}')
return return
data = interaction.data if not interaction.data or 'values' not in interaction.data:
if not data or 'values' not in data: logging.warning('[MENU] No data in select callback')
logging.warning('[VIBE] No data in select callback')
return return
data_value = data['values'][0] data_value = interaction.data['values'][0]
if data_value not in ( if data_value not in (
'fun', 'active', 'calm', 'sad', 'all', 'fun', 'active', 'calm', 'sad', 'all',
'favorite', 'popular', 'discover', 'default', 'favorite', 'popular', 'discover', 'default',
'not-russian', 'russian', 'without-words', 'any' 'not-russian', 'russian', 'without-words', 'any'
): ):
logging.warning(f'[VIBE] Unknown data_value: {data_value}') logging.warning(f'[MENU] Unknown data_value: {data_value}')
return return
logging.info(f"[VIBE] Settings option '{custom_id}' updated to {data_value}") logging.info(f"[MENU] Settings option '{custom_id}' updated to {data_value}")
await self.users_db.update(interaction.user.id, {f'vibe_settings.{custom_id}': data_value}) await self.users_db.update(interaction.user.id, {f'vibe_settings.{custom_id}': data_value})
view = MyVibeSettingsView(interaction) view = MyVibeSettingsView(interaction)
@@ -262,16 +242,15 @@ class MyVibeSettingsView(View, VoiceExtension):
def __init__(self, interaction: Interaction, *items: Item, timeout: float | None = 360, disable_on_timeout: bool = True): def __init__(self, interaction: Interaction, *items: Item, timeout: float | None = 360, disable_on_timeout: bool = True):
View.__init__(self, *items, timeout=timeout, disable_on_timeout=disable_on_timeout) View.__init__(self, *items, timeout=timeout, disable_on_timeout=disable_on_timeout)
VoiceExtension.__init__(self, None) VoiceExtension.__init__(self, None)
self.interaction = interaction self.interaction = interaction
async def init(self) -> None: async def init(self) -> Self:
if not self.interaction.user: if not self.interaction.user:
logging.warning('[VIBE] No user in settings view') logging.warning('[MENU] No user in settings view')
return return self
settings = (await self.users_db.get_user(self.interaction.user.id, projection={'vibe_settings'}))['vibe_settings'] settings = (await self.users_db.get_user(self.interaction.user.id, projection={'vibe_settings'}))['vibe_settings']
diversity_settings = settings['diversity'] diversity_settings = settings['diversity']
diversity = [ diversity = [
SelectOption(label='Любое', value='default'), SelectOption(label='Любое', value='default'),
@@ -279,7 +258,7 @@ class MyVibeSettingsView(View, VoiceExtension):
SelectOption(label='Незнакомое', value='discover', default=diversity_settings == 'discover'), SelectOption(label='Незнакомое', value='discover', default=diversity_settings == 'discover'),
SelectOption(label='Популярное', value='popular', default=diversity_settings == 'popular') SelectOption(label='Популярное', value='popular', default=diversity_settings == 'popular')
] ]
mood_settings = settings['mood'] mood_settings = settings['mood']
mood = [ mood = [
SelectOption(label='Любое', value='all'), SelectOption(label='Любое', value='all'),
@@ -288,7 +267,7 @@ class MyVibeSettingsView(View, VoiceExtension):
SelectOption(label='Спокойное', value='calm', default=mood_settings == 'calm'), SelectOption(label='Спокойное', value='calm', default=mood_settings == 'calm'),
SelectOption(label='Грустное', value='sad', default=mood_settings == 'sad') SelectOption(label='Грустное', value='sad', default=mood_settings == 'sad')
] ]
lang_settings = settings['lang'] lang_settings = settings['lang']
lang = [ lang = [
SelectOption(label='Любое', value='any'), SelectOption(label='Любое', value='any'),
@@ -321,17 +300,19 @@ class MyVibeSettingsView(View, VoiceExtension):
for select in [feel_select, mood_select, lang_select]: for select in [feel_select, mood_select, lang_select]:
self.add_item(select) self.add_item(select)
return self
class MyVibeSettingsButton(Button, VoiceExtension): class MyVibeSettingsButton(Button, VoiceExtension):
def __init__(self, **kwargs): def __init__(self, **kwargs):
Button.__init__(self, **kwargs) super().__init__(**kwargs)
VoiceExtension.__init__(self, None) VoiceExtension.__init__(self, None)
async def callback(self, interaction: Interaction) -> None: async def callback(self, interaction: Interaction) -> None:
logging.info('[VIBE] My vibe settings button callback') logging.info('[MENU] My vibe settings button callback')
if not await self.voice_check(interaction): if not await self.voice_check(interaction, check_vibe_privilage=True):
return return
await interaction.respond('Настройки "Моей Волны"', view=MyVibeSettingsView(interaction), ephemeral=True) await interaction.respond('Настройки "Моей Волны"', view=await MyVibeSettingsView(interaction).init(), ephemeral=True)
class AddToPlaylistSelect(Select, VoiceExtension): class AddToPlaylistSelect(Select, VoiceExtension):
def __init__(self, ym_client: YMClient, *args, **kwargs): def __init__(self, ym_client: YMClient, *args, **kwargs):
@@ -340,8 +321,11 @@ class AddToPlaylistSelect(Select, VoiceExtension):
self.ym_client = ym_client self.ym_client = ym_client
async def callback(self, interaction: Interaction): async def callback(self, interaction: Interaction):
if not await self.voice_check(interaction, check_vibe_privilage=False): logging.info('[MENU] Add to playlist select callback')
if not await self.voice_check(interaction):
return return
if not interaction.guild_id or not interaction.data or 'values' not in interaction.data: if not interaction.guild_id or not interaction.data or 'values' not in interaction.data:
logging.warning('[MENU] No data in select callback') logging.warning('[MENU] No data in select callback')
return return
@@ -351,19 +335,17 @@ class AddToPlaylistSelect(Select, VoiceExtension):
playlist = cast(Playlist, await self.ym_client.users_playlists(kind=data[0], user_id=data[1])) playlist = cast(Playlist, await self.ym_client.users_playlists(kind=data[0], user_id=data[1]))
current_track = await self.db.get_track(interaction.guild_id, 'current') current_track = await self.db.get_track(interaction.guild_id, 'current')
if not current_track: if not current_track:
return return
try: res = await self.ym_client.users_playlists_insert_track(
res = await self.ym_client.users_playlists_insert_track( kind=f"{playlist.kind}",
kind=f"{playlist.kind}", track_id=current_track['id'],
track_id=current_track['id'], album_id=current_track['albums'][0]['id'],
album_id=current_track['albums'][0]['id'], revision=playlist.revision or 1,
revision=playlist.revision or 1, user_id=f"{playlist.uid}"
user_id=f"{playlist.uid}" )
)
except yandex_music.exceptions.NetworkError:
res = None
if res: if res:
await interaction.respond('✅ Добавлено в плейлист', delete_after=15, ephemeral=True) await interaction.respond('✅ Добавлено в плейлист', delete_after=15, ephemeral=True)
@@ -376,7 +358,7 @@ class AddToPlaylistButton(Button, VoiceExtension):
VoiceExtension.__init__(self, None) VoiceExtension.__init__(self, None)
async def callback(self, interaction: Interaction): async def callback(self, interaction: Interaction):
if not await self.voice_check(interaction, check_vibe_privilage=False) or not interaction.guild_id: if not await self.voice_check(interaction) or not interaction.guild_id:
return return
client = await self.init_ym_client(interaction) client = await self.init_ym_client(interaction)
@@ -412,14 +394,14 @@ class MenuView(View, VoiceExtension):
VoiceExtension.__init__(self, None) VoiceExtension.__init__(self, None)
self.ctx = ctx self.ctx = ctx
self.repeat_button = ToggleRepeatButton(style=ButtonStyle.secondary, emoji='🔂', row=0) self.repeat_button = ToggleButton(style=ButtonStyle.secondary, emoji='🔂', row=0, custom_id='repeat')
self.shuffle_button = ToggleShuffleButton(style=ButtonStyle.secondary, emoji='🔀', row=0) self.shuffle_button = ToggleButton(style=ButtonStyle.secondary, emoji='🔀', row=0, custom_id='shuffle')
self.play_pause_button = PlayPauseButton(style=ButtonStyle.primary, 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.next_button = SwitchTrackButton(style=ButtonStyle.primary, emoji='', row=0, custom_id='next')
self.prev_button = PrevTrackButton(style=ButtonStyle.primary, emoji='', row=0) self.prev_button = SwitchTrackButton(style=ButtonStyle.primary, emoji='', row=0, custom_id='previous')
self.like_button = LikeButton(style=ButtonStyle.secondary, emoji='❤️', row=1) self.like_button = ReactionButton(style=ButtonStyle.secondary, emoji='❤️', row=1, custom_id='like')
self.dislike_button = DislikeButton(style=ButtonStyle.secondary, emoji='💔', row=1) self.dislike_button = ReactionButton(style=ButtonStyle.secondary, emoji='💔', row=1, custom_id='dislike')
self.lyrics_button = LyricsButton(style=ButtonStyle.secondary, emoji='📋', row=1) self.lyrics_button = LyricsButton(style=ButtonStyle.secondary, emoji='📋', row=1)
self.add_to_playlist_button = AddToPlaylistButton(style=ButtonStyle.secondary, emoji='📁', row=1) self.add_to_playlist_button = AddToPlaylistButton(style=ButtonStyle.secondary, emoji='📁', row=1)
self.vibe_button = MyVibeButton(style=ButtonStyle.secondary, emoji='🌊', row=1) self.vibe_button = MyVibeButton(style=ButtonStyle.secondary, emoji='🌊', row=1)
@@ -474,7 +456,7 @@ class MenuView(View, VoiceExtension):
return self return self
async def on_timeout(self) -> None: async def on_timeout(self) -> None:
logging.debug('Menu timed out...') logging.debug('[MENU] Menu timed out. Deleting menu message')
if not self.ctx.guild_id: if not self.ctx.guild_id:
return return
@@ -484,6 +466,6 @@ class MenuView(View, VoiceExtension):
message = await self.get_menu_message(self.ctx, self.guild['current_menu']) message = await self.get_menu_message(self.ctx, self.guild['current_menu'])
if message: if message:
await message.delete() await message.delete()
logging.debug('Successfully deleted menu message') logging.debug('[MENU] Successfully deleted menu message')
else: else:
logging.debug('No menu message found') logging.debug('[MENU] No menu message found')