impr: Minor code improvement.

This commit is contained in:
Lemon4ksan
2025-01-31 17:26:08 +03:00
parent b3962c8928
commit 956dc4d925
8 changed files with 73 additions and 55 deletions

View File

@@ -25,22 +25,23 @@ async def get_search_suggestions(ctx: discord.AutocompleteContext) -> list[str]:
users_db = BaseUsersDatabase() users_db = BaseUsersDatabase()
token = users_db.get_ym_token(ctx.interaction.user.id) token = users_db.get_ym_token(ctx.interaction.user.id)
if not token: if not token:
return ['❌ Укажите токен через /account login.'] logging.info(f"[GENERAL] User {ctx.interaction.user.id} has no token")
return []
try: try:
client = await YMClient(token).init() client = await YMClient(token).init()
except yandex_music.exceptions.UnauthorizedError: except yandex_music.exceptions.UnauthorizedError:
logging.info(f"[GENERAL] User {ctx.interaction.user.id} provided invalid token") logging.info(f"[GENERAL] User {ctx.interaction.user.id} provided invalid token")
return ['❌ Недействительный токен.'] return []
content_type = ctx.options['тип'] content_type = ctx.options['тип']
search = await client.search(ctx.value) search = await client.search(ctx.value)
if not search: if not search:
logging.warning(f"Failed to search for '{ctx.value}' for user {ctx.interaction.user.id}") logging.warning(f"[GENERAL] Failed to search for '{ctx.value}' for user {ctx.interaction.user.id}")
return ["❌ Что-то пошло не так. Повторите попытку позже"] return []
res = [] res = []
logging.debug(f"Searching for '{ctx.value}' for user {ctx.interaction.user.id}") logging.debug(f"[GENERAL] Searching for '{ctx.value}' for user {ctx.interaction.user.id}")
if content_type == 'Трек' and search.tracks: if content_type == 'Трек' and search.tracks:
for item in search.tracks.results: for item in search.tracks.results:
@@ -57,10 +58,9 @@ async def get_search_suggestions(ctx: discord.AutocompleteContext) -> list[str]:
elif content_type == "Свой плейлист": elif content_type == "Свой плейлист":
if not client.me or not client.me.account or not client.me.account.uid: if not client.me or not client.me.account or not client.me.account.uid:
logging.warning(f"Failed to get playlists for user {ctx.interaction.user.id}") logging.warning(f"Failed to get playlists for user {ctx.interaction.user.id}")
return ["❌ Что-то пошло не так. Повторите попытку позже"] else:
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
@@ -95,7 +95,8 @@ class General(Cog):
embed.description = ( embed.description = (
"Этот бот позволяет слушать музыку из вашего аккаунта Yandex Music.\n" "Этот бот позволяет слушать музыку из вашего аккаунта Yandex Music.\n"
"Зарегистрируйте свой токен с помощью /login. Его можно получить [здесь](https://github.com/MarshalX/yandex-music-api/discussions/513).\n" "Зарегистрируйте свой токен с помощью /login. Его можно получить [здесь](https://github.com/MarshalX/yandex-music-api/discussions/513).\n"
"Для получения помощи по конкретной команде, введите /help <команда>.\n\n" "Для получения помощи по конкретной команде, введите /help <команда>.\n"
"Для изменения настроек необходимо иметь права управления каналами на сервере.\n\n"
"**Для дополнительной помощи, присоединяйтесь к [серверу любителей Яндекс Музыки](https://discord.gg/gkmFDaPMeC).**" "**Для дополнительной помощи, присоединяйтесь к [серверу любителей Яндекс Музыки](https://discord.gg/gkmFDaPMeC).**"
) )
@@ -152,15 +153,18 @@ class General(Cog):
embed.description += ( embed.description += (
"`Примечание`: Если вы один в голосовом канале или имеете разрешение управления каналом, голосование не начинается.\n\n" "`Примечание`: Если вы один в голосовом канале или имеете разрешение управления каналом, голосование не начинается.\n\n"
"Переключиться на следующий трек в очереди. \n```/track next```\n" "Переключиться на следующий трек в очереди. \n```/track next```\n"
"Приостановить текущий трек.\n ```/track pause```\n" "Приостановить текущий трек.\n```/track pause```\n"
"Возобновить текущий трек.\n ```/track resume```\n" "Возобновить текущий трек.\n```/track resume```\n"
"Прервать проигрывание, удалить историю, очередь и текущий плеер.\n ```/track stop```" "Прервать проигрывание, удалить историю, очередь и текущий плеер.\n ```/track stop```\n"
"Запустить Мою Волну по текущему треку.\n```/track vibe```"
) )
elif command == 'voice': elif command == 'voice':
embed.description += ( embed.description += (
"Присоединить бота в голосовой канал. Требует разрешения управления каналом.\n ```/voice join```\n" "`Примечание`: Доступность меню и Моей Волны зависит от настроек сервера.\n\n"
"Присоединить бота в голосовой канал. Требует разрешения управления каналом.\n```/voice join```\n"
"Заставить бота покинуть голосовой канал. Требует разрешения управления каналом.\n ```/voice leave```\n" "Заставить бота покинуть голосовой канал. Требует разрешения управления каналом.\n ```/voice leave```\n"
"Создать меню проигрывателя. Доступность зависит от настроек сервера. По умолчанию работает только когда в канале один человек.\n```/voice menu```" "Создать меню проигрывателя. По умолчанию работает только когда в канале один человек.\n```/voice menu```\n"
"Запустить Мою Волну. По умолчанию работает только когда в канале один человек.\n```/vibe```"
) )
else: else:
response_message = '❌ Неизвестная команда.' response_message = '❌ Неизвестная команда.'
@@ -194,16 +198,19 @@ class General(Cog):
@account.command(description="Получить плейлист «Мне нравится»") @account.command(description="Получить плейлист «Мне нравится»")
async def likes(self, ctx: discord.ApplicationContext) -> None: async def likes(self, ctx: discord.ApplicationContext) -> None:
logging.info(f"[GENERAL] Likes command invoked by user {ctx.author.id} in guild {ctx.guild.id}") logging.info(f"[GENERAL] Likes command invoked by user {ctx.author.id} in guild {ctx.guild.id}")
token = self.users_db.get_ym_token(ctx.user.id) token = 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}") logging.info(f"[GENERAL] No token found for user {ctx.user.id}")
await ctx.respond('❌ Необходимо указать свой токен доступа с помощью команды /login.', delete_after=15, ephemeral=True) await ctx.respond("❌ Укажите токен через /account login.", delete_after=15, ephemeral=True)
return return
client = await YMClient(token).init() client = await YMClient(token).init()
if not client.me or not client.me.account or not client.me.account.uid: if not client.me or not client.me.account or not client.me.account.uid:
logging.warning(f"Failed to fetch user info for user {ctx.user.id}") logging.warning(f"Failed to fetch user info for user {ctx.user.id}")
await ctx.respond('❌ Что-то пошло не так. Повторите попытку позже.', delete_after=15, ephemeral=True) await ctx.respond('❌ Что-то пошло не так. Повторите попытку позже.', delete_after=15, ephemeral=True)
return return
likes = await client.users_likes_tracks() likes = await client.users_likes_tracks()
if likes is None: if likes is None:
logging.info(f"[GENERAL] Failed to fetch likes for user {ctx.user.id}") logging.info(f"[GENERAL] Failed to fetch likes for user {ctx.user.id}")
@@ -226,7 +233,7 @@ class General(Cog):
token = self.users_db.get_ym_token(ctx.user.id) token = self.users_db.get_ym_token(ctx.user.id)
if not token: if not token:
await ctx.respond('❌ Необходимо указать свой токен доступа с помощью команды /login.', delete_after=15, ephemeral=True) await ctx.respond("❌ Укажите токен через /account login.", delete_after=15, ephemeral=True)
return return
client = await YMClient(token).init() client = await YMClient(token).init()
@@ -272,7 +279,7 @@ class General(Cog):
token = self.users_db.get_ym_token(ctx.user.id) token = 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}") logging.info(f"[GENERAL] No token found for user {ctx.user.id}")
await ctx.respond("❌ Укажите токен через /account login.", ephemeral=True) await ctx.respond("❌ Укажите токен через /account login.", delete_after=15, ephemeral=True)
return return
try: try:
@@ -311,7 +318,7 @@ class General(Cog):
embed = await generate_item_embed(result) embed = await generate_item_embed(result)
view = ListenView(result) view = ListenView(result)
else: else:
result = await client.search(name, True) result = await client.search(name, nocorrect=True)
if not result: if not result:
logging.warning(f"Failed to search for '{name}' for user {ctx.user.id}") logging.warning(f"Failed to search for '{name}' for user {ctx.user.id}")
@@ -368,4 +375,3 @@ class General(Cog):
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)

View File

@@ -11,7 +11,7 @@ from yandex_music import Track, Album, Artist, Playlist, Label
from discord import Embed from discord import Embed
async def generate_item_embed(item: Track | Album | Artist | Playlist | list[Track], vibing: bool = False) -> Embed: async def generate_item_embed(item: Track | Album | Artist | Playlist | list[Track], vibing: bool = False) -> Embed:
"""Generate item embed. """Generate item embed. list[Track] is used for likes.
Args: Args:
item (yandex_music.Track | yandex_music.Album | yandex_music.Artist | yandex_music.Playlist): Item to be processed. item (yandex_music.Track | yandex_music.Album | yandex_music.Artist | yandex_music.Playlist): Item to be processed.

View File

@@ -11,7 +11,7 @@ from discord.ui import View
from discord import Interaction, ApplicationContext, RawReactionActionEvent from discord import Interaction, ApplicationContext, RawReactionActionEvent
from MusicBot.cogs.utils import generate_item_embed from MusicBot.cogs.utils import generate_item_embed
from MusicBot.database import VoiceGuildsDatabase, BaseUsersDatabase from MusicBot.database import VoiceGuildsDatabase, BaseUsersDatabase, ExplicitGuild
# TODO: RawReactionActionEvent is poorly supported. # TODO: RawReactionActionEvent is poorly supported.
@@ -277,7 +277,7 @@ class VoiceExtension:
token = self.users_db.get_ym_token(ctx.user.id) token = self.users_db.get_ym_token(ctx.user.id)
if not token: if not token:
logging.debug(f"[VC_EXT] No token found for user {ctx.user.id}") logging.debug(f"[VC_EXT] No token found for user {ctx.user.id}")
await ctx.respond("Необходимо указать свой токен доступа с помощью команды /login.", delete_after=15, ephemeral=True) await ctx.respond("Укажите токен через /account login.", delete_after=15, ephemeral=True)
return False return False
if not isinstance(ctx.channel, discord.VoiceChannel): if not isinstance(ctx.channel, discord.VoiceChannel):
@@ -389,32 +389,22 @@ class VoiceExtension:
self.db.update(gid, {'current_track': track.to_dict()}) self.db.update(gid, {'current_track': track.to_dict()})
guild = self.db.get_guild(gid) guild = self.db.get_guild(gid)
if guild['current_menu'] and not isinstance(ctx, RawReactionActionEvent):
if menu_message:
try:
if gid in menu_views:
menu_views[gid].stop()
menu_views[gid] = await MenuView(ctx).init()
await menu_message.edit(embed=await generate_item_embed(track, guild['vibing']), view=menu_views[gid])
except discord.errors.NotFound:
logging.warning("[VC_EXT] Menu message not found. Using 'update_menu_embed' instead.")
await self._retry_update_menu_embed(ctx, guild['current_menu'], button_callback)
else:
await self._retry_update_menu_embed(ctx, guild['current_menu'], button_callback)
try: try:
await track.download_async(f'music/{gid}.mp3') await asyncio.gather(
song = discord.FFmpegPCMAudio(f'music/{gid}.mp3', options='-vn -filter:a "volume=0.15"') track.download_async(f'music/{gid}.mp3'),
except yandex_music.exceptions.TimedOutError: # sometimes track takes too long to download. self._update_menu(ctx, guild, track, menu_message, button_callback)
)
except yandex_music.exceptions.TimedOutError:
logging.warning(f"[VC_EXT] Timed out while downloading track '{track.title}'") logging.warning(f"[VC_EXT] Timed out while downloading track '{track.title}'")
if not isinstance(ctx, RawReactionActionEvent) and ctx.user and ctx.channel: if not isinstance(ctx, RawReactionActionEvent) and ctx.user and ctx.channel:
channel = cast(discord.VoiceChannel, ctx.channel) channel = cast(discord.VoiceChannel, ctx.channel)
if not retry: if not retry:
channel = cast(discord.VoiceChannel, ctx.channel)
return await self.play_track(ctx, track, vc=vc, button_callback=button_callback, retry=True) return await self.play_track(ctx, track, vc=vc, button_callback=button_callback, retry=True)
await channel.send(f"😔 Не удалось загрузить трек. Попробуйте сбросить меню.", delete_after=15) await channel.send(f"😔 Не удалось загрузить трек. Попробуйте сбросить меню.", delete_after=15)
return None return None
song = discord.FFmpegPCMAudio(f'music/{gid}.mp3', options='-vn -filter:a "volume=0.15"')
vc.play(song, after=lambda exc: asyncio.run_coroutine_threadsafe(self.next_track(ctx, after=True), loop)) vc.play(song, after=lambda exc: asyncio.run_coroutine_threadsafe(self.next_track(ctx, after=True), loop))
logging.info(f"[VC_EXT] Playing track '{track.title}'") logging.info(f"[VC_EXT] Playing track '{track.title}'")
@@ -718,6 +708,30 @@ class VoiceExtension:
break break
await asyncio.sleep(0.25) await asyncio.sleep(0.25)
update = await self.update_menu_embed(ctx, menu_mid, button_callback) update = await self.update_menu_embed(ctx, menu_mid, button_callback)
async def _update_menu(
self,
ctx: ApplicationContext | Interaction | RawReactionActionEvent,
guild: ExplicitGuild,
track: Track,
menu_message: discord.Message | None,
button_callback: bool
) -> None:
from MusicBot.ui import MenuView
gid = cast(int, ctx.guild_id)
if guild['current_menu'] and not isinstance(ctx, RawReactionActionEvent):
if menu_message:
try:
if gid in menu_views:
menu_views[gid].stop()
menu_views[gid] = await MenuView(ctx).init()
await menu_message.edit(embed=await generate_item_embed(track, guild['vibing']), view=menu_views[gid])
except discord.errors.NotFound:
logging.warning("[VC_EXT] Menu message not found. Using 'update_menu_embed' instead.")
await self._retry_update_menu_embed(ctx, guild['current_menu'], button_callback)
else:
await self._retry_update_menu_embed(ctx, guild['current_menu'], button_callback)
async def init_ym_client(self, ctx: ApplicationContext | Interaction | RawReactionActionEvent, token: str | None = None) -> YMClient | None: async def init_ym_client(self, ctx: ApplicationContext | Interaction | RawReactionActionEvent, token: str | None = None) -> YMClient | None:
"""Initialize Yandex Music client. Return client on success. Return None if no token found and respond to the context. """Initialize Yandex Music client. Return client on success. Return None if no token found and respond to the context.
@@ -737,7 +751,7 @@ class VoiceExtension:
if not token: if not token:
logging.debug("No token found in 'init_ym_client'") logging.debug("No token found in 'init_ym_client'")
if not isinstance(ctx, discord.RawReactionActionEvent): if not isinstance(ctx, discord.RawReactionActionEvent):
await ctx.respond("❌ Укажите токен через /account login.", ephemeral=True) await ctx.respond("❌ Укажите токен через /account login.", delete_after=15, ephemeral=True)
return None return None
try: try:
@@ -747,4 +761,4 @@ class VoiceExtension:
if not isinstance(ctx, discord.RawReactionActionEvent): if not isinstance(ctx, discord.RawReactionActionEvent):
await ctx.respond("❌ Недействительный токен. Если это не так, попробуйте ещё раз.", delete_after=15, ephemeral=True) await ctx.respond("❌ Недействительный токен. Если это не так, попробуйте ещё раз.", delete_after=15, ephemeral=True)
return None return None
return client return client

View File

@@ -356,15 +356,19 @@ class Voice(Cog, VoiceExtension):
token = user['ym_token'] token = user['ym_token']
if not token: if not token:
logging.info(f"[VOICE] User {ctx.user.id} has no YM token") logging.info(f"[VOICE] User {ctx.user.id} has no YM token")
await ctx.respond("❌ Укажите токен через /account login.", ephemeral=True) await ctx.respond("❌ Укажите токен через /account login.", delete_after=15, ephemeral=True)
return return
client = await self.init_ym_client(ctx, user['ym_token']) client = await self.init_ym_client(ctx, user['ym_token'])
if not client: if not client:
logging.info(f"[VOICE] Failed to init YM client for user {ctx.user.id}")
await ctx.respond("❌ Что-то пошло не так. Попробуйте позже.", delete_after=15, ephemeral=True)
return return
track = guild['current_track'] track = guild['current_track']
if not track: if not track:
logging.info(f"[VOICE] No current track in guild {ctx.guild.id}")
await ctx.respond("❌ Что-то пошло не так. Попробуйте позже.", delete_after=15, ephemeral=True)
return return
res = await client.rotor_station_feedback_track_finished( res = await client.rotor_station_feedback_track_finished(
@@ -470,7 +474,7 @@ class Voice(Cog, VoiceExtension):
await self.send_menu_message(ctx) await self.send_menu_message(ctx)
await self.update_vibe(ctx, 'track', guild['current_track']['id']) await self.update_vibe(ctx, 'track', guild['current_track']['id'])
@discord.slash_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:
logging.info(f"[VOICE] Vibe (user) command invoked by user {ctx.user.id} in guild {ctx.guild_id}") logging.info(f"[VOICE] Vibe (user) command invoked by user {ctx.user.id} in guild {ctx.guild_id}")
if not await self.voice_check(ctx): if not await self.voice_check(ctx):

View File

@@ -165,7 +165,7 @@ class MyVibeButton(Button, VoiceExtension):
) )
class ListenView(View): class ListenView(View):
def __init__(self, item: Track | Album | Artist | Playlist | list[Track], *items: Item, timeout: float | None = 3600, disable_on_timeout: bool = False): 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) super().__init__(*items, timeout=timeout, disable_on_timeout=disable_on_timeout)
logging.debug(f"Creating view for type: '{type(item).__name__}'") logging.debug(f"Creating view for type: '{type(item).__name__}'")

View File

@@ -234,7 +234,7 @@ class MyVibeSelect(Select, VoiceExtension):
await interaction.edit(view=view) await interaction.edit(view=view)
class MyVibeSettingsView(View, VoiceExtension): class MyVibeSettingsView(View, VoiceExtension):
def __init__(self, interaction: Interaction, *items: Item, timeout: float | None = 360, disable_on_timeout: bool = False): 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)

View File

@@ -2,12 +2,7 @@ from math import ceil
from typing import Any from typing import Any
from discord.ui import View, Button, Item from discord.ui import View, Button, Item
from discord import ButtonStyle, Interaction, Embed from discord import ApplicationContext, ButtonStyle, Interaction, Embed
from MusicBot.cogs.utils.voice_extension import VoiceExtension
from discord.ui import View, Button, Item
from discord import ButtonStyle, Interaction, ApplicationContext
from MusicBot.cogs.utils.voice_extension import VoiceExtension from MusicBot.cogs.utils.voice_extension import VoiceExtension
@@ -71,7 +66,7 @@ class MPPrevButton(Button, VoiceExtension):
await interaction.edit(embed=embed, view=MyPlaylists(interaction)) await interaction.edit(embed=embed, view=MyPlaylists(interaction))
class MyPlaylists(View, VoiceExtension): class MyPlaylists(View, VoiceExtension):
def __init__(self, ctx: ApplicationContext | Interaction, *items: Item, timeout: float | None = 3600, disable_on_timeout: bool = True): 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) View.__init__(self, *items, timeout=timeout, disable_on_timeout=disable_on_timeout)
VoiceExtension.__init__(self, None) VoiceExtension.__init__(self, None)
if not ctx.user: if not ctx.user:
@@ -121,7 +116,7 @@ class QueuePrevButton(Button, VoiceExtension):
await interaction.edit(embed=embed, view=QueueView(interaction)) await interaction.edit(embed=embed, view=QueueView(interaction))
class QueueView(View, VoiceExtension): class QueueView(View, VoiceExtension):
def __init__(self, ctx: ApplicationContext | Interaction, *items: Item, timeout: float | None = 3600, disable_on_timeout: bool = True): 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) View.__init__(self, *items, timeout=timeout, disable_on_timeout=disable_on_timeout)
VoiceExtension.__init__(self, None) VoiceExtension.__init__(self, None)
if not ctx.user or not ctx.guild: if not ctx.user or not ctx.guild:

View File

@@ -4,5 +4,4 @@ PyNaCl
pymongo pymongo
yandex-music yandex-music
pillow pillow
python-dotenv python-dotenv
wavelink