impr: Add more error handling.

This commit is contained in:
Lemon4ksan
2025-02-27 22:42:19 +03:00
parent 48fe0b894b
commit 3c1b0ec266
8 changed files with 204 additions and 237 deletions

View File

@@ -18,7 +18,7 @@ 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:
if not ctx.interaction.user or not ctx.value or not (100 > len(ctx.value) > 2):
return []
uid = ctx.interaction.user.id
@@ -41,6 +41,10 @@ async def get_search_suggestions(ctx: discord.AutocompleteContext) -> list[str]:
logging.debug(f"[GENERAL] Searching for '{ctx.value}' for user {uid}")
if content_type not in ('Трек', 'Альбом', 'Артист', 'Плейлист'):
logging.error(f"[GENERAL] Invalid content type '{content_type}' for user {uid}")
return []
if content_type == 'Трек' and search.tracks is not None:
res = [f"{item.title} {f"({item.version})" if item.version else ''} - {", ".join(item.artists_name())}" for item in search.tracks.results]
elif content_type == 'Альбом' and search.albums is not None:
@@ -50,13 +54,13 @@ async def get_search_suggestions(ctx: discord.AutocompleteContext) -> list[str]:
elif content_type == 'Плейлист' and search.playlists is not None:
res = [f"{item.title}" for item in search.playlists.results]
else:
logging.warning(f"[GENERAL] Invalid content type '{content_type}' for user {uid}")
logging.info(f"[GENERAL] Failed to get content type '{content_type}' with name '{ctx.value}' for user {uid}")
return []
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:
if not ctx.interaction.user or not ctx.value or not (100 > len(ctx.value) > 2):
return []
uid = ctx.interaction.user.id
@@ -70,21 +74,25 @@ async def get_user_playlists_suggestions(ctx: discord.AutocompleteContext) -> li
except UnauthorizedError:
logging.info(f"[GENERAL] User {uid} provided invalid token")
return []
logging.debug(f"[GENERAL] Searching for '{ctx.value}' for user {uid}")
playlists_list = await client.users_playlists_list()
logging.debug(f"[GENERAL] Searching for '{ctx.value}' for user {uid}")
try:
playlists_list = await client.users_playlists_list()
except Exception as e:
logging.error(f"[GENERAL] Failed to get playlists for user {uid}: {e}")
return []
return [playlist.title for playlist in playlists_list if playlist.title and ctx.value in playlist.title][:100]
class General(Cog):
def __init__(self, bot: discord.Bot):
self.bot = bot
self.db = BaseGuildsDatabase()
self.users_db = users_db
account = discord.SlashCommandGroup("account", "Команды, связанные с аккаунтом.")
@discord.slash_command(description="Получить информацию о командах YandexMusic.")
@discord.option(
"command",
@@ -208,10 +216,11 @@ class General(Cog):
await ctx.respond("❌ Укажите токен через /account login.", delete_after=15, ephemeral=True)
return
client = await YMClient(token).init()
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}")
await ctx.respond('❌ Что-то пошло не так. Повторите попытку позже.', delete_after=15, ephemeral=True)
try:
client = await YMClient(token).init()
except UnauthorizedError:
logging.info(f"[GENERAL] Invalid token for user {ctx.user.id}")
await ctx.respond('❌ Неверный токен. Укажите новый через /account login.', delete_after=15, ephemeral=True)
return
likes = await client.users_likes_tracks()
@@ -256,7 +265,7 @@ class General(Cog):
client = await YMClient(token).init()
except UnauthorizedError:
logging.info(f"[GENERAL] User {ctx.user.id} provided invalid token")
await ctx.respond("❌ Недействительный токен. Если это не так, попробуйте ещё раз.", delete_after=15, ephemeral=True)
await ctx.respond('❌ Неверный токен. Укажите новый через /account login.', delete_after=15, ephemeral=True)
return
search = await client.search(content_type, type_='playlist')
@@ -287,7 +296,7 @@ class General(Cog):
autocomplete=discord.utils.basic_autocomplete(get_user_playlists_suggestions)
)
async def playlist(self, ctx: discord.ApplicationContext, name: str) -> None:
logging.info(f"[GENERAL] Playlists command invoked by user {ctx.user.id} in guild {ctx.guild_id}")
logging.info(f"[GENERAL] Playlist command invoked by user {ctx.user.id} in guild {ctx.guild_id}")
token = await self.users_db.get_ym_token(ctx.user.id)
if not token:
@@ -299,10 +308,15 @@ class General(Cog):
client = await YMClient(token).init()
except UnauthorizedError:
logging.info(f"[GENERAL] User {ctx.user.id} provided invalid token")
await ctx.respond("❌ Недействительный токен. Если это не так, попробуйте ещё раз.", delete_after=15, ephemeral=True)
await ctx.respond('❌ Неверный токен. Укажите новый через /account login.', delete_after=15, ephemeral=True)
return
playlists = await client.users_playlists_list()
try:
playlists = await client.users_playlists_list()
except UnauthorizedError:
logging.warning(f"[GENERAL] Unknown token error for user {ctx.user.id}")
await ctx.respond("❌ Произошла неизвестная ошибка при попытке получения плейлистов. Пожалуйста, сообщите об этом разработчику.", delete_after=15, ephemeral=True)
return
playlist = next((playlist for playlist in playlists if playlist.title == name), None)
if not playlist:

View File

@@ -1,5 +1,6 @@
import logging
from typing import cast
from functools import lru_cache
from typing import cast, Final
from math import ceil
from os import getenv
@@ -10,29 +11,35 @@ from PIL import Image
from yandex_music import Track, Album, Artist, Playlist, Label
from discord import Embed
explicit_eid: Final[str | None] = getenv('EXPLICIT_EID')
if not explicit_eid:
raise ValueError('You must specify explicit emoji id in your enviroment (EXPLICIT_EID).')
async def generate_item_embed(item: Track | Album | Artist | Playlist | list[Track], vibing: bool = False) -> Embed:
"""Generate item embed. list[Track] is used for likes. If vibing is True, add vibing image.
Args:
item (yandex_music.Track | yandex_music.Album | yandex_music.Artist | yandex_music.Playlist): Item to be processed.
item (Track | Album | Artist | Playlist | list[Track]): Item to be processed.
vibing (bool, optional): Add vibing image. Defaults to False.
Returns:
discord.Embed: Item embed.
"""
logging.debug(f"[EMBEDS] Generating embed for type: '{type(item).__name__}'")
if isinstance(item, Track):
embed = await _generate_track_embed(item)
elif isinstance(item, Album):
embed = await _generate_album_embed(item)
elif isinstance(item, Artist):
embed = await _generate_artist_embed(item)
elif isinstance(item, Playlist):
embed = await _generate_playlist_embed(item)
elif isinstance(item, list):
embed = _generate_likes_embed(item)
else:
raise ValueError(f"Unknown item type: {type(item).__name__}")
match item:
case Track():
embed = await _generate_track_embed(item)
case Album():
embed = await _generate_album_embed(item)
case Artist():
embed = await _generate_artist_embed(item)
case Playlist():
embed = await _generate_playlist_embed(item)
case list():
embed = _generate_likes_embed(item)
case _:
raise ValueError(f"Unknown item type: {type(item).__name__}")
if vibing:
embed.set_image(
@@ -41,13 +48,12 @@ async def generate_item_embed(item: Track | Album | Artist | Playlist | list[Tra
return embed
def _generate_likes_embed(tracks: list[Track]) -> Embed:
track_count = len(tracks)
cover_url = "https://avatars.yandex.net/get-music-user-playlist/11418140/favorit-playlist-cover.bb48fdb9b9f4/300x300"
embed = Embed(
title="Мне нравится",
description="Треки, которые вам понравились.",
color=0xce3a26,
color=0xce3a26
)
embed.set_thumbnail(url=cover_url)
@@ -56,203 +62,143 @@ def _generate_likes_embed(tracks: list[Track]) -> Embed:
if track.duration_ms:
duration += track.duration_ms
duration_m = duration // 60000
duration_s = ceil(duration / 1000) - duration_m * 60
if duration_s == 60:
duration_m += 1
duration_s = 0
embed.add_field(name="Длительность", value=f"{duration_m}:{duration_s:02}")
if track_count is not None:
embed.add_field(name="Треки", value=str(track_count))
embed.add_field(name="Длительность", value=_format_duration(duration))
embed.add_field(name="Треки", value=str(len(tracks)))
return embed
async def _generate_track_embed(track: Track) -> Embed:
title = cast(str, track.title)
avail = cast(bool, track.available)
artists = track.artists_name()
title = track.title
albums = [cast(str, album.title) for album in track.albums]
lyrics = cast(bool, track.lyrics_available)
duration = cast(int, track.duration_ms)
explicit = track.explicit or track.content_warning
bg_video = track.background_video_uri
metadata = track.meta_data
year = track.albums[0].year
artist = track.artists[0]
year = track.albums[0].year if track.albums else None
artist = track.artists[0] if track.artists else None
cover_url = track.get_cover_url('400x400')
color = await _get_average_color_from_url(cover_url)
if track.cover_uri:
cover_url = track.get_cover_url('400x400')
color = await _get_average_color_from_url(cover_url)
else:
cover_url = None
color = 0x000
if explicit:
explicit_eid = getenv('EXPLICIT_EID')
if not explicit_eid:
raise ValueError('You must specify explicit emoji id in your enviroment (EXPLICIT_EID).')
if explicit and title:
title += ' <:explicit:' + explicit_eid + '>'
duration_m = duration // 60000
duration_s = ceil(duration / 1000) - duration_m * 60
if duration_s == 60:
duration_m += 1
duration_s = 0
if artist:
artist_url = f"https://music.yandex.ru/artist/{artist.id}"
artist_cover = artist.cover
artist_url = f"https://music.yandex.ru/artist/{artist.id}"
artist_cover = artist.cover
if not artist_cover and artist.op_image:
artist_cover_url = artist.get_op_image_url()
elif artist_cover:
artist_cover_url = artist_cover.get_url()
if not artist_cover and artist.op_image:
artist_cover_url = artist.get_op_image_url()
elif artist_cover:
artist_cover_url = artist_cover.get_url()
else:
artist_cover_url = None
else:
artist_url = None
artist_cover_url = None
embed = Embed(
title=title,
description=", ".join(albums),
color=color,
color=color
)
embed.set_thumbnail(url=cover_url)
embed.set_author(name=", ".join(artists), url=artist_url, icon_url=artist_cover_url)
embed.set_author(name=", ".join(track.artists_name()), url=artist_url, icon_url=artist_cover_url)
embed.add_field(name="Текст песни", value="Есть" if lyrics else "Нет")
embed.add_field(name="Длительность", value=f"{duration_m}:{duration_s:02}")
embed.add_field(name="Текст песни", value="Есть" if track.lyrics_available else "Нет")
if isinstance(track.duration_ms, int):
embed.add_field(name="Длительность", value=_format_duration(track.duration_ms))
if year:
embed.add_field(name="Год выпуска", value=str(year))
if metadata:
if metadata.year:
embed.add_field(name="Год выхода", value=str(metadata.year))
if track.background_video_uri:
embed.add_field(name="Видеофон", value=f"[Ссылка]({track.background_video_uri})")
if metadata.number:
embed.add_field(name="Позиция", value=str(metadata.number))
if metadata.composer:
embed.add_field(name="Композитор", value=metadata.composer)
if metadata.version:
embed.add_field(name="Версия", value=metadata.version)
if bg_video:
embed.add_field(name="Видеофон", value=f"[Ссылка]({bg_video})")
if not avail:
if not (track.available or track.available_for_premium_users):
embed.set_footer(text=f"Трек в данный момент недоступен.")
return embed
async def _generate_album_embed(album: Album) -> Embed:
title = cast(str, album.title)
track_count = album.track_count
artists = album.artists_name()
avail = cast(bool, album.available)
description = album.short_description
year = album.year
version = album.version
bests = album.bests
duration = album.duration_ms
title = album.title
explicit = album.explicit or album.content_warning
likes_count = album.likes_count
artist = album.artists[0]
cover_url = album.get_cover_url('400x400')
color = await _get_average_color_from_url(cover_url)
if isinstance(album.labels[0], Label):
labels = [cast(Label, label).name for label in album.labels]
else:
labels = [cast(str, label) for label in album.labels]
if version:
title += f' *{version}*'
if album.version and title:
title += f' *{album.version}*'
if explicit:
explicit_eid = getenv('EXPLICIT_EID')
if not explicit_eid:
raise ValueError('You must specify explicit emoji id in your enviroment.')
if explicit and title:
title += ' <:explicit:' + explicit_eid + '>'
artist_url = f"https://music.yandex.ru/artist/{artist.id}"
artist_cover = artist.cover
if not artist_cover and artist.op_image:
artist_cover_url = artist.get_op_image_url()
artist_cover_url = artist.get_op_image_url('400x400')
elif artist_cover:
artist_cover_url = artist_cover.get_url()
artist_cover_url = artist_cover.get_url(size='400x400')
else:
artist_cover_url = None
embed = Embed(
title=title,
description=description,
color=color,
description=album.short_description,
color=await _get_average_color_from_url(cover_url)
)
embed.set_thumbnail(url=cover_url)
embed.set_author(name=", ".join(artists), url=artist_url, icon_url=artist_cover_url)
embed.set_author(name=", ".join(album.artists_name()), url=artist_url, icon_url=artist_cover_url)
if year:
embed.add_field(name="Год выпуска", value=str(year))
if album.year:
embed.add_field(name="Год выпуска", value=str(album.year))
if duration:
duration_m = duration // 60000
duration_s = ceil(duration / 1000) - duration_m * 60
if duration_s == 60:
duration_m += 1
duration_s = 0
embed.add_field(name="Длительность", value=f"{duration_m}:{duration_s:02}")
if isinstance(album.duration_ms, int):
embed.add_field(name="Длительность", value=_format_duration(album.duration_ms))
if track_count is not None:
if track_count > 1:
embed.add_field(name="Треки", value=str(track_count))
else:
embed.add_field(name="Треки", value="Сингл")
if album.track_count is not None:
embed.add_field(name="Треки", value=str(album.track_count) if album.track_count > 1 else "Сингл")
if likes_count:
embed.add_field(name="Лайки", value=str(likes_count))
if album.likes_count is not None:
embed.add_field(name="Лайки", value=str(album.likes_count))
if len(labels) > 1:
embed.add_field(name="Лейблы", value=", ".join(labels))
else:
embed.add_field(name="Лейбл", value=", ".join(labels))
embed.add_field(name="Лейблы" if len(labels) > 1 else "Лейбл", value=", ".join(labels))
if not avail:
if not (album.available or album.available_for_premium_users):
embed.set_footer(text=f"Альбом в данный момент недоступен.")
return embed
async def _generate_artist_embed(artist: Artist) -> Embed:
name = cast(str, artist.name)
likes_count = artist.likes_count
avail = cast(bool, artist.available)
counts = artist.counts
description = artist.description
ratings = artist.ratings
popular_tracks = artist.popular_tracks
if not artist.cover:
cover_url = artist.get_op_image_url('400x400')
else:
cover_url = artist.cover.get_url(size='400x400')
color = await _get_average_color_from_url(cover_url)
embed = Embed(
title=name,
description=description.text if description else None,
color=color,
title=artist.name,
description=artist.description.text if artist.description else None,
color=await _get_average_color_from_url(cover_url)
)
embed.set_thumbnail(url=cover_url)
if likes_count:
embed.add_field(name="Лайки", value=str(likes_count))
if artist.likes_count:
embed.add_field(name="Лайки", value=str(artist.likes_count))
# if ratings:
# embed.add_field(name="Слушателей за месяц", value=str(ratings.month)) # Wrong numbers?
# embed.add_field(name="Слушателей за месяц", value=str(ratings.month)) # Wrong numbers
if counts:
embed.add_field(name="Треки", value=str(counts.tracks))
if artist.counts:
embed.add_field(name="Треки", value=str(artist.counts.tracks))
embed.add_field(name="Альбомы", value=str(counts.direct_albums))
embed.add_field(name="Альбомы", value=str(artist.counts.direct_albums))
if artist.genres:
genres = [genre.capitalize() for genre in artist.genres]
@@ -261,23 +207,12 @@ async def _generate_artist_embed(artist: Artist) -> Embed:
else:
embed.add_field(name="Жанр", value=", ".join(genres))
if not avail:
if not artist.available or artist.reason:
embed.set_footer(text=f"Артист в данный момент недоступен.")
return embed
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
year = playlist.created
modified = playlist.modified
duration = playlist.duration_ms
likes_count = playlist.likes_count
cover_url = None
if playlist.cover and playlist.cover.uri:
cover_url = f"https://{playlist.cover.uri.replace('%%', '400x400')}"
else:
@@ -287,6 +222,8 @@ async def _generate_playlist_embed(playlist: Playlist) -> Embed:
if track and track.albums and track.albums[0].cover_uri:
cover_url = f"https://{track.albums[0].cover_uri.replace('%%', '400x400')}"
break
else:
cover_url = None
if cover_url:
color = await _get_average_color_from_url(cover_url)
@@ -294,37 +231,33 @@ async def _generate_playlist_embed(playlist: Playlist) -> Embed:
color = 0x000
embed = Embed(
title=title,
description=description,
color=color,
title=playlist.title,
description=playlist.description,
color=color
)
embed.set_thumbnail(url=cover_url)
if year:
embed.add_field(name="Год создания", value=str(year).split('-')[0])
if playlist.created:
embed.add_field(name="Год создания", value=str(playlist.created).split('-')[0])
if modified:
embed.add_field(name="Изменён", value=str(modified).split('-')[0])
if playlist.modified:
embed.add_field(name="Изменён", value=str(playlist.modified).split('-')[0])
if duration:
duration_m = duration // 60000
duration_s = ceil(duration / 1000) - duration_m * 60
if duration_s == 60:
duration_m += 1
duration_s = 0
embed.add_field(name="Длительность", value=f"{duration_m}:{duration_s:02}")
if playlist.duration_ms:
embed.add_field(name="Длительность", value=_format_duration(playlist.duration_ms))
if track_count is not None:
embed.add_field(name="Треки", value=str(track_count))
if playlist.track_count is not None:
embed.add_field(name="Треки", value=str(playlist.track_count))
if likes_count:
embed.add_field(name="Лайки", value=str(likes_count))
if playlist.likes_count:
embed.add_field(name="Лайки", value=str(playlist.likes_count))
if not avail:
if not playlist.available:
embed.set_footer(text=f"Плейлист в данный момент недоступен.")
return embed
@lru_cache()
async def _get_average_color_from_url(url: str) -> int:
"""Get image from url and calculate its average color to use in embeds.
@@ -358,5 +291,13 @@ async def _get_average_color_from_url(url: str) -> int:
b = b_total // count
return (r << 16) + (g << 8) + b
except Exception:
except (aiohttp.ClientError, IOError, ValueError):
return 0x000
def _format_duration(duration_ms: int) -> str:
duration_m = duration_ms // 60000
duration_s = ceil(duration_ms / 1000) - duration_m * 60
if duration_s == 60:
duration_m += 1
duration_s = 0
return f"{duration_m}:{duration_s:02}"

View File

@@ -163,6 +163,7 @@ class VoiceExtension:
guild = await self.db.get_guild(gid, projection={'vibing': 1, 'current_menu': 1, 'current_track': 1})
if not guild['current_menu']:
logging.debug("[VC_EXT] No current menu found")
return False
menu_message = await self.get_menu_message(ctx, guild['current_menu']) if not menu_message else menu_message
@@ -281,7 +282,7 @@ class VoiceExtension:
uid = viber_id if viber_id else ctx.user_id if isinstance(ctx, discord.RawReactionActionEvent) else ctx.user.id if ctx.user else None
if not uid or not gid:
logging.warning("[VC_EXT] Guild ID or User ID not found in context inside 'vibe_update'")
logging.warning("[VC_EXT] Guild ID or User ID not found in context")
return False
user = await self.users_db.get_user(uid, projection={'ym_token': 1, 'vibe_settings': 1})
@@ -516,6 +517,7 @@ class VoiceExtension:
vc: discord.VoiceClient | None = None,
*,
after: bool = False,
client: YMClient | None = None,
menu_message: discord.Message | None = None,
button_callback: bool = False
) -> str | None:
@@ -526,6 +528,7 @@ class VoiceExtension:
ctx (ApplicationContext | Interaction | RawReactionActionEvent): Context
vc (discord.VoiceClient, optional): Voice client.
after (bool, optional): Whether the function is being called by the after callback. Defaults to False.
client (YMClient | None, optional): Yandex Music client. Defaults to None.
menu_message (discord.Message | None): Menu message. If None, fetches menu from channel using message id from database. Defaults to None.
button_callback (bool, optional): Should be True if the function is being called from button callback. Defaults to False.
@@ -548,13 +551,6 @@ class VoiceExtension:
logging.debug("[VC_EXT] Playback is stopped, skipping after callback.")
return None
if not (client := await self.init_ym_client(ctx, user['ym_token'])):
return None
if not (vc := await self.get_voice_client(ctx) if not vc else vc):
logging.debug("[VC_EXT] Voice client not found in 'next_track'")
return None
if guild['current_track'] and guild['current_menu'] and not guild['repeat']:
logging.debug("[VC_EXT] Adding current track to history")
await self.db.modify_track(gid, guild['current_track'], 'previous', 'insert')

View File

@@ -204,8 +204,8 @@ class Voice(Cog, VoiceExtension):
@voice.command(name="menu", description="Создать или обновить меню проигрывателя.")
async def menu(self, ctx: discord.ApplicationContext) -> None:
logging.info(f"[VOICE] Menu command invoked by user {ctx.author.id} in guild {ctx.guild.id}")
if await self.voice_check(ctx):
await self.send_menu_message(ctx)
if await self.voice_check(ctx) and not await self.send_menu_message(ctx):
await ctx.respond("Не удалось создать меню.", ephemeral=True)
@voice.command(name="join", description="Подключиться к голосовому каналу, в котором вы сейчас находитесь.")
async def join(self, ctx: discord.ApplicationContext) -> None:
@@ -213,17 +213,17 @@ class Voice(Cog, VoiceExtension):
member = cast(discord.Member, ctx.author)
guild = await self.db.get_guild(ctx.guild.id, projection={'allow_change_connect': 1})
vc = await self.get_voice_client(ctx)
await ctx.defer(ephemeral=True)
if not member.guild_permissions.manage_channels and not guild['allow_change_connect']:
response_message = "У вас нет прав для выполнения этой команды."
elif vc and vc.is_connected():
response_message = "❌ Бот уже находится в голосовом канале. Выключите его с помощью команды /voice leave."
elif isinstance(ctx.channel, discord.VoiceChannel):
try:
await ctx.channel.connect()
except TimeoutError:
response_message = "Не удалось подключиться к голосовому каналу."
except discord.ClientException:
response_message = "❌ Бот уже находится в голосовом канале. Выключите его с помощью команды /voice leave."
else:
response_message = "✅ Подключение успешно!"
else:
@@ -302,6 +302,10 @@ class Voice(Cog, VoiceExtension):
await self.users_db.update(ctx.user.id, {'queue_page': 0})
tracks = await self.db.get_tracks_list(ctx.guild.id, 'next')
if len(tracks) == 0:
await ctx.respond("❌ Очередь пуста.", ephemeral=True)
return
embed = generate_queue_embed(0, tracks)
await ctx.respond(embed=embed, view=await QueueView(ctx).init(), ephemeral=True)
@@ -340,6 +344,7 @@ class Voice(Cog, VoiceExtension):
)
return
await ctx.defer(ephemeral=True)
res = await self.stop_playing(ctx, full=True)
if res:
await ctx.respond("✅ Воспроизведение остановлено.", delete_after=15, ephemeral=True)
@@ -435,18 +440,16 @@ class Voice(Cog, VoiceExtension):
}
)
return
feedback = await self.update_vibe(ctx, _type, _id)
if not feedback:
if not await self.update_vibe(ctx, _type, _id):
await ctx.respond("❌ Операция не удалась. Возможно, у вес нет подписки на Яндекс Музыку.", delete_after=15, ephemeral=True)
return
if guild['current_menu']:
await ctx.respond("✅ Моя Волна включена.", delete_after=15, ephemeral=True)
else:
await self.send_menu_message(ctx, disable=True)
elif not await self.send_menu_message(ctx, disable=True):
await ctx.respond("Не удалось отправить меню. Попробуйте позже.", delete_after=15, ephemeral=True)
next_track = await self.db.get_track(ctx.guild_id, 'next')
if next_track:
await self._play_track(ctx, next_track)
await self.play_track(ctx, next_track)

View File

@@ -5,7 +5,6 @@ import discord
from discord.ext.commands import Bot
intents = discord.Intents.default()
intents.message_content = True
bot = Bot(intents=intents)
cogs_list = [

View File

@@ -19,11 +19,11 @@ class PlayButton(Button, VoiceExtension):
logging.debug(f"[FIND] Callback triggered for type: '{type(self.item).__name__}'")
if not interaction.guild:
logging.warning("[FIND] No guild found in PlayButton callback")
logging.info("[FIND] No guild found in PlayButton callback")
await interaction.respond("❌ Эта команда доступна только на серверах.", ephemeral=True, delete_after=15)
return
if not await self.voice_check(interaction):
logging.debug("[FIND] Voice check failed in PlayButton callback")
return
gid = interaction.guild.id
@@ -41,7 +41,7 @@ class PlayButton(Button, VoiceExtension):
album = await self.item.with_tracks_async()
if not album or not album.volumes:
logging.debug("[FIND] Failed to fetch album tracks in PlayButton callback")
await interaction.respond("Не удалось получить треки альбома.", ephemeral=True)
await interaction.respond("Не удалось получить треки альбома.", ephemeral=True, delete_after=15)
return
tracks = [track for volume in album.volumes for track in volume]
@@ -53,7 +53,7 @@ class PlayButton(Button, VoiceExtension):
artist_tracks = await self.item.get_tracks_async()
if not artist_tracks:
logging.debug("[FIND] Failed to fetch artist tracks in PlayButton callback")
await interaction.respond("Не удалось получить треки артиста.", ephemeral=True)
await interaction.respond("Не удалось получить треки артиста.", ephemeral=True, delete_after=15)
return
tracks = artist_tracks.tracks.copy()
@@ -65,7 +65,7 @@ class PlayButton(Button, VoiceExtension):
short_tracks = await self.item.fetch_tracks_async()
if not short_tracks:
logging.debug("[FIND] Failed to fetch playlist tracks in PlayButton callback")
await interaction.respond("Не удалось получить треки из плейлиста.", delete_after=15)
await interaction.respond("Не удалось получить треки из плейлиста.", ephemeral=True, delete_after=15)
return
tracks = [cast(Track, short_track.track) for short_track in short_tracks]
@@ -77,7 +77,7 @@ class PlayButton(Button, VoiceExtension):
tracks = self.item.copy()
if not tracks:
logging.debug("[FIND] Empty tracks list in PlayButton callback")
await interaction.respond("Не удалось получить треки.", delete_after=15)
await interaction.respond("Не удалось получить треки.", ephemeral=True, delete_after=15)
return
action = 'add_playlist'
@@ -109,21 +109,20 @@ class PlayButton(Button, VoiceExtension):
)
return
logging.debug(f"[FIND] Skipping vote for '{action}'")
if guild['current_menu']:
await interaction.respond(response_message, delete_after=15)
else:
await self.send_menu_message(interaction, disable=True)
elif not await self.send_menu_message(interaction, disable=True):
await interaction.respond('Не удалось отправить сообщение.', ephemeral=True, delete_after=15)
if guild['current_track'] is not None:
if guild['current_track']:
logging.debug(f"[FIND] Adding tracks to queue")
await self.db.modify_track(gid, tracks, 'next', 'extend')
else:
logging.debug(f"[FIND] Playing track")
track = tracks.pop(0)
await self.db.modify_track(gid, tracks, 'next', 'extend')
await self.play_track(interaction, track)
if not await self.play_track(interaction, track):
await interaction.respond('Не удалось воспроизвести трек.', ephemeral=True, delete_after=15)
if interaction.message:
await interaction.message.delete()
@@ -146,6 +145,7 @@ class MyVibeButton(Button, VoiceExtension):
logging.warning(f"[VIBE] Guild ID is None in button callback")
return
track_type_map = {
Track: 'track', Album: 'album', Artist: 'artist', Playlist: 'playlist', list: 'user'
}
@@ -153,7 +153,7 @@ class MyVibeButton(Button, VoiceExtension):
if isinstance(self.item, Playlist):
if not self.item.owner:
logging.warning(f"[VIBE] Playlist owner is None")
await interaction.respond("Не удалось получить информацию о плейлисте.", ephemeral=True)
await interaction.respond("Не удалось получить информацию о плейлисте. Отсутствует владелец.", ephemeral=True, delete_after=15)
return
_id = self.item.owner.login + '_' + str(self.item.kind)
@@ -162,12 +162,11 @@ class MyVibeButton(Button, VoiceExtension):
else:
_id = 'onyourwave'
await self.send_menu_message(interaction, disable=True)
await self.update_vibe(
interaction,
track_type_map[type(self.item)],
_id
)
guild = await self.db.get_guild(gid, projection={'current_menu': 1})
if not guild['current_menu'] and not await self.send_menu_message(interaction, disable=True):
await interaction.respond('Не удалось отправить сообщение.', ephemeral=True, delete_after=15)
await self.update_vibe(interaction, track_type_map[type(self.item)], _id)
next_track = await self.db.get_track(gid, 'next')
if next_track:
@@ -208,6 +207,6 @@ class ListenView(View):
async def on_timeout(self) -> None:
try:
return await super().on_timeout()
except discord.NotFound:
except discord.HTTPException:
pass
self.stop()

View File

@@ -2,7 +2,7 @@ import logging
from typing import Self, cast
from discord.ui import View, Button, Item, Select
from discord import VoiceChannel, ButtonStyle, Interaction, ApplicationContext, RawReactionActionEvent, Embed, ComponentType, SelectOption, Member
from discord import VoiceChannel, ButtonStyle, Interaction, ApplicationContext, RawReactionActionEvent, Embed, ComponentType, SelectOption, Member, HTTPException
import yandex_music.exceptions
from yandex_music import TrackLyrics, Playlist, ClientAsync as YMClient
@@ -348,7 +348,7 @@ class MyVibeSelect(Select, VoiceExtension):
await interaction.edit(view=view)
class MyVibeSettingsView(View, VoiceExtension):
def __init__(self, interaction: Interaction, *items: Item, timeout: float | None = None, 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)
VoiceExtension.__init__(self, None)
self.interaction = interaction
@@ -410,6 +410,13 @@ class MyVibeSettingsView(View, VoiceExtension):
self.add_item(select)
return self
async def on_timeout(self) -> None:
try:
return await super().on_timeout()
except HTTPException:
pass
self.stop()
class MyVibeSettingsButton(Button, VoiceExtension):
def __init__(self, **kwargs):
@@ -530,7 +537,7 @@ class MenuView(View, VoiceExtension):
if not self.ctx.guild_id:
return self
self.guild = await self.db.get_guild(self.ctx.guild_id)
self.guild = await self.db.get_guild(self.ctx.guild_id, projection={'repeat': 1, 'shuffle': 1, 'current_track': 1, 'vibing': 1})
if self.guild['repeat']:
self.repeat_button.style = ButtonStyle.success

View File

@@ -2,7 +2,7 @@ from math import ceil
from typing import Self, Any
from discord.ui import View, Button, Item
from discord import ApplicationContext, ButtonStyle, Interaction, Embed
from discord import ApplicationContext, ButtonStyle, Interaction, Embed, HTTPException
from MusicBot.cogs.utils.voice_extension import VoiceExtension
@@ -27,10 +27,11 @@ class QueueNextButton(Button, VoiceExtension):
def __init__(self, **kwargs):
Button.__init__(self, **kwargs)
VoiceExtension.__init__(self, None)
async def callback(self, interaction: Interaction) -> None:
if not interaction.user or not interaction.guild:
return
user = await self.users_db.get_user(interaction.user.id)
page = user['queue_page'] + 1
await self.users_db.update(interaction.user.id, {'queue_page': page})
@@ -42,10 +43,11 @@ class QueuePrevButton(Button, VoiceExtension):
def __init__(self, **kwargs):
Button.__init__(self, **kwargs)
VoiceExtension.__init__(self, None)
async def callback(self, interaction: Interaction) -> None:
if not interaction.user or not interaction.guild:
return
user = await self.users_db.get_user(interaction.user.id)
page = user['queue_page'] - 1
await self.users_db.update(interaction.user.id, {'queue_page': page})
@@ -54,30 +56,36 @@ class QueuePrevButton(Button, VoiceExtension):
await interaction.edit(embed=embed, view=await QueueView(interaction).init())
class QueueView(View, VoiceExtension):
def __init__(self, ctx: ApplicationContext | Interaction, *items: Item, timeout: float | None = 360, disable_on_timeout: bool = True):
def __init__(self, ctx: ApplicationContext | Interaction, *items: Item, timeout: float | None = 360, disable_on_timeout: bool = False):
View.__init__(self, *items, timeout=timeout, disable_on_timeout=disable_on_timeout)
VoiceExtension.__init__(self, None)
self.ctx = ctx
self.next_button = QueueNextButton(style=ButtonStyle.primary, emoji='▶️')
self.prev_button = QueuePrevButton(style=ButtonStyle.primary, emoji='◀️')
async def init(self) -> Self:
if not self.ctx.user or not self.ctx.guild:
return self
tracks = await self.db.get_tracks_list(self.ctx.guild.id, 'next')
user = await self.users_db.get_user(self.ctx.user.id)
count = 15 * user['queue_page']
if not tracks[count + 15:]:
self.next_button.disabled = True
if not tracks[:count]:
self.prev_button.disabled = True
self.add_item(self.prev_button)
self.add_item(self.next_button)
return self
async def on_timeout(self) -> None:
try:
await super().on_timeout()
except HTTPException:
pass
self.stop()