mirror of
https://github.com/deadcxap/YandexMusicDiscordBot.git
synced 2026-01-09 23:51:45 +03:00
impr: Remove explicit checks due to poor support and uselessness.
This commit is contained in:
@@ -1,19 +1,16 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Literal, cast
|
from typing import Literal
|
||||||
from asyncio import gather
|
from asyncio import gather
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
from discord.ext.commands import Cog
|
from discord.ext.commands import Cog
|
||||||
|
|
||||||
import yandex_music
|
from yandex_music.exceptions import UnauthorizedError
|
||||||
import yandex_music.exceptions
|
|
||||||
from yandex_music import ClientAsync as YMClient
|
from yandex_music import ClientAsync as YMClient
|
||||||
from yandex_music import Track, Album, Artist, Playlist
|
|
||||||
|
|
||||||
from MusicBot.database import BaseUsersDatabase, BaseGuildsDatabase
|
|
||||||
|
|
||||||
from MusicBot.ui import ListenView
|
from MusicBot.ui import ListenView
|
||||||
from MusicBot.cogs.utils.embeds import generate_item_embed
|
from MusicBot.database import BaseUsersDatabase, BaseGuildsDatabase
|
||||||
|
from MusicBot.cogs.utils import generate_item_embed
|
||||||
|
|
||||||
users_db = BaseUsersDatabase()
|
users_db = BaseUsersDatabase()
|
||||||
|
|
||||||
@@ -24,41 +21,37 @@ 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 len(ctx.value) < 2:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
token = await users_db.get_ym_token(ctx.interaction.user.id)
|
uid = ctx.interaction.user.id
|
||||||
|
token = await users_db.get_ym_token(uid)
|
||||||
if not token:
|
if not token:
|
||||||
logging.info(f"[GENERAL] User {ctx.interaction.user.id} has no token")
|
logging.info(f"[GENERAL] User {uid} has no token")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
client = await YMClient(token).init()
|
client = await YMClient(token).init()
|
||||||
except yandex_music.exceptions.UnauthorizedError:
|
except UnauthorizedError:
|
||||||
logging.info(f"[GENERAL] User {ctx.interaction.user.id} provided invalid token")
|
logging.info(f"[GENERAL] User {uid} 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"[GENERAL] Failed to search for '{ctx.value}' for user {ctx.interaction.user.id}")
|
logging.warning(f"[GENERAL] Failed to search for '{ctx.value}' for user {uid}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
res = []
|
logging.debug(f"[GENERAL] Searching for '{ctx.value}' for user {uid}")
|
||||||
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:
|
res = [f"{item.title} {f"({item.version})" if item.version else ''} - {", ".join(item.artists_name())}" for item in search.tracks.results]
|
||||||
res.append(f"{item.title} {f"({item.version})" if item.version else ''} - {", ".join(item.artists_name())}")
|
|
||||||
elif content_type == 'Альбом' and search.albums:
|
elif content_type == 'Альбом' and search.albums:
|
||||||
for item in search.albums.results:
|
res = [f"{item.title} - {", ".join(item.artists_name())}" for item in search.albums.results]
|
||||||
res.append(f"{item.title} - {", ".join(item.artists_name())}")
|
|
||||||
elif content_type == 'Артист' and search.artists:
|
elif content_type == 'Артист' and search.artists:
|
||||||
for item in search.artists.results:
|
res = [f"{item.name}" for item in search.artists.results]
|
||||||
res.append(f"{item.name}")
|
|
||||||
elif content_type == 'Плейлист' and search.playlists:
|
elif content_type == 'Плейлист' and search.playlists:
|
||||||
for item in search.playlists.results:
|
res = [f"{item.title}" for item in search.playlists.results]
|
||||||
res.append(f"{item.title}")
|
else:
|
||||||
elif content_type == "Свой плейлист":
|
logging.warning(f"[GENERAL] Invalid content type '{content_type}' for user {uid}")
|
||||||
playlists_list = await client.users_playlists_list()
|
return []
|
||||||
res = [playlist.title if playlist.title else 'Без названия' for playlist in playlists_list]
|
|
||||||
|
|
||||||
return res[:100]
|
return res[:100]
|
||||||
|
|
||||||
@@ -66,17 +59,20 @@ async def get_user_playlists_suggestions(ctx: discord.AutocompleteContext) -> li
|
|||||||
if not ctx.interaction.user or not ctx.value or len(ctx.value) < 2:
|
if not ctx.interaction.user or not ctx.value or len(ctx.value) < 2:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
token = await users_db.get_ym_token(ctx.interaction.user.id)
|
uid = ctx.interaction.user.id
|
||||||
|
token = await users_db.get_ym_token(uid)
|
||||||
if not token:
|
if not token:
|
||||||
logging.info(f"[GENERAL] User {ctx.interaction.user.id} has no token")
|
logging.info(f"[GENERAL] User {uid} has no token")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
client = await YMClient(token).init()
|
client = await YMClient(token).init()
|
||||||
except yandex_music.exceptions.UnauthorizedError:
|
except UnauthorizedError:
|
||||||
logging.info(f"[GENERAL] User {ctx.interaction.user.id} provided invalid token")
|
logging.info(f"[GENERAL] User {uid} provided invalid token")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
logging.debug(f"[GENERAL] Searching for '{ctx.value}' for user {uid}")
|
||||||
|
|
||||||
playlists_list = await client.users_playlists_list()
|
playlists_list = await client.users_playlists_list()
|
||||||
return [playlist.title for playlist in playlists_list if playlist.title and ctx.value in playlist.title][:100]
|
return [playlist.title for playlist in playlists_list if playlist.title and ctx.value in playlist.title][:100]
|
||||||
|
|
||||||
@@ -94,12 +90,11 @@ class General(Cog):
|
|||||||
"command",
|
"command",
|
||||||
description="Название команды.",
|
description="Название команды.",
|
||||||
type=discord.SlashCommandOptionType.string,
|
type=discord.SlashCommandOptionType.string,
|
||||||
default='all'
|
required=False
|
||||||
)
|
)
|
||||||
async def help(self, ctx: discord.ApplicationContext, command: str) -> None:
|
async def help(self, ctx: discord.ApplicationContext, command: str = 'all') -> None:
|
||||||
logging.info(f"[GENERAL] Help command invoked by {ctx.user.id} for command '{command}'")
|
logging.info(f"[GENERAL] Help command invoked by {ctx.user.id} for command '{command}'")
|
||||||
|
|
||||||
response_message = None
|
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title='Помощь',
|
title='Помощь',
|
||||||
color=0xfed42b
|
color=0xfed42b
|
||||||
@@ -113,10 +108,8 @@ class General(Cog):
|
|||||||
"Зарегистрируйте свой токен с помощью /login. Его можно получить [здесь](https://github.com/MarshalX/yandex-music-api/discussions/513).\n"
|
"Зарегистрируйте свой токен с помощью /login. Его можно получить [здесь](https://github.com/MarshalX/yandex-music-api/discussions/513).\n"
|
||||||
"Для получения помощи по конкретной команде, введите /help <команда>.\n"
|
"Для получения помощи по конкретной команде, введите /help <команда>.\n"
|
||||||
"Для изменения настроек необходимо иметь права управления каналами на сервере.\n\n"
|
"Для изменения настроек необходимо иметь права управления каналами на сервере.\n\n"
|
||||||
"Помните, что это **не замена Яндекс Музыки**, а лишь её дополнение. Не ожидайте безупречного звука.\n\n"
|
"**Для дополнительной помощи, присоединяйтесь к [серверу сообщества](https://discord.gg/gkmFDaPMeC).**"
|
||||||
"**Для дополнительной помощи, присоединяйтесь к [серверу любителей Яндекс Музыки](https://discord.gg/gkmFDaPMeC).**"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name='__Основные команды__',
|
name='__Основные команды__',
|
||||||
value="""`account`
|
value="""`account`
|
||||||
@@ -127,7 +120,6 @@ class General(Cog):
|
|||||||
`track`
|
`track`
|
||||||
`voice`"""
|
`voice`"""
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.set_footer(text='©️ Bananchiki')
|
embed.set_footer(text='©️ Bananchiki')
|
||||||
elif command == 'account':
|
elif command == 'account':
|
||||||
embed.description += (
|
embed.description += (
|
||||||
@@ -145,8 +137,7 @@ class General(Cog):
|
|||||||
)
|
)
|
||||||
elif command == 'help':
|
elif command == 'help':
|
||||||
embed.description += (
|
embed.description += (
|
||||||
"Вывести список всех команд.\n```/help```\n"
|
"Вывести список всех команд или информацию по конкретной команде.\n```/help <команда>```\n"
|
||||||
"Получить информацию о конкретной команде.\n```/help <команда>```"
|
|
||||||
)
|
)
|
||||||
elif command == 'queue':
|
elif command == 'queue':
|
||||||
embed.description += (
|
embed.description += (
|
||||||
@@ -158,7 +149,6 @@ class General(Cog):
|
|||||||
embed.description += (
|
embed.description += (
|
||||||
"`Примечание`: Только пользователи с разрешением управления каналом могут менять настройки.\n\n"
|
"`Примечание`: Только пользователи с разрешением управления каналом могут менять настройки.\n\n"
|
||||||
"Получить текущие настройки.\n```/settings show```\n"
|
"Получить текущие настройки.\n```/settings show```\n"
|
||||||
"Разрешить или запретить воспроизведение Explicit треков и альбомов. Если автор или плейлист содержат Explicit треки, убираются кнопки для доступа к ним.\n```/settings explicit```\n"
|
|
||||||
"Разрешить или запретить создание меню проигрывателя, когда в канале больше одного человека.\n```/settings menu```\n"
|
"Разрешить или запретить создание меню проигрывателя, когда в канале больше одного человека.\n```/settings menu```\n"
|
||||||
"Разрешить или запретить голосование.\n```/settings vote <тип>```\n"
|
"Разрешить или запретить голосование.\n```/settings vote <тип>```\n"
|
||||||
"Разрешить или запретить отключение/подключение бота к каналу участникам без прав управления каналом.\n```/settings connect```\n"
|
"Разрешить или запретить отключение/подключение бота к каналу участникам без прав управления каналом.\n```/settings connect```\n"
|
||||||
@@ -182,10 +172,10 @@ class General(Cog):
|
|||||||
"Запустить станцию. Без уточнения станции, запускает Мою Волну.\n```/voice vibe <название станции>```"
|
"Запустить станцию. Без уточнения станции, запускает Мою Волну.\n```/voice vibe <название станции>```"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
response_message = '❌ Неизвестная команда.'
|
await ctx.respond('❌ Неизвестная команда.')
|
||||||
embed = None
|
return
|
||||||
|
|
||||||
await ctx.respond(response_message, embed=embed, ephemeral=True)
|
await ctx.respond(embed=embed, ephemeral=True)
|
||||||
|
|
||||||
@account.command(description="Ввести токен Яндекс Музыки.")
|
@account.command(description="Ввести токен Яндекс Музыки.")
|
||||||
@discord.option("token", type=discord.SlashCommandOptionType.string, description="Токен.")
|
@discord.option("token", type=discord.SlashCommandOptionType.string, description="Токен.")
|
||||||
@@ -193,16 +183,20 @@ class General(Cog):
|
|||||||
logging.info(f"[GENERAL] Login command invoked by user {ctx.author.id} in guild {ctx.guild.id}")
|
logging.info(f"[GENERAL] Login command invoked by user {ctx.author.id} in guild {ctx.guild.id}")
|
||||||
try:
|
try:
|
||||||
client = await YMClient(token).init()
|
client = await YMClient(token).init()
|
||||||
except yandex_music.exceptions.UnauthorizedError:
|
except UnauthorizedError:
|
||||||
logging.info(f"[GENERAL] Invalid token provided by user {ctx.author.id}")
|
logging.info(f"[GENERAL] Invalid token provided by user {ctx.author.id}")
|
||||||
await ctx.respond('❌ Недействительный токен.', delete_after=15, ephemeral=True)
|
await ctx.respond('❌ Недействительный токен.', delete_after=15, ephemeral=True)
|
||||||
return
|
return
|
||||||
about = cast(yandex_music.Status, client.me).to_dict()
|
|
||||||
uid = ctx.author.id
|
|
||||||
|
|
||||||
await self.users_db.update(uid, {'ym_token': token})
|
if not client.me or not client.me.account:
|
||||||
logging.info(f"[GENERAL] Token saved for user {ctx.author.id}")
|
logging.warning(f"[GENERAL] Failed to get user info for user {ctx.author.id}")
|
||||||
await ctx.respond(f'Привет, {about['account']['first_name']}!', delete_after=15, ephemeral=True)
|
await ctx.respond('❌ Не удалось получить информацию о пользователе.', delete_after=15, ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
await self.users_db.update(ctx.author.id, {'ym_token': token})
|
||||||
|
await ctx.respond(f'✅ Привет, {client.me.account.first_name}!', delete_after=15, ephemeral=True)
|
||||||
|
|
||||||
|
logging.info(f"[GENERAL] User {ctx.author.id} logged in successfully")
|
||||||
|
|
||||||
@account.command(description="Удалить токен из базы данных бота.")
|
@account.command(description="Удалить токен из базы данных бота.")
|
||||||
async def remove(self, ctx: discord.ApplicationContext) -> None:
|
async def remove(self, ctx: discord.ApplicationContext) -> None:
|
||||||
@@ -213,7 +207,8 @@ class General(Cog):
|
|||||||
return
|
return
|
||||||
|
|
||||||
await self.users_db.update(ctx.user.id, {'ym_token': None})
|
await self.users_db.update(ctx.user.id, {'ym_token': None})
|
||||||
await ctx.respond(f'Токен был удалён.', delete_after=15, ephemeral=True)
|
await ctx.respond(f'✅ Токен был удалён.', delete_after=15, ephemeral=True)
|
||||||
|
logging.info(f"[GENERAL] Token removed for user {ctx.author.id}")
|
||||||
|
|
||||||
@account.command(description="Получить плейлист «Мне нравится»")
|
@account.command(description="Получить плейлист «Мне нравится»")
|
||||||
async def likes(self, ctx: discord.ApplicationContext) -> None:
|
async def likes(self, ctx: discord.ApplicationContext) -> None:
|
||||||
@@ -240,12 +235,12 @@ class General(Cog):
|
|||||||
logging.info(f"[GENERAL] Empty likes for user {ctx.user.id}")
|
logging.info(f"[GENERAL] Empty likes for user {ctx.user.id}")
|
||||||
await ctx.respond('❌ У вас нет треков в плейлисте «Мне нравится».', delete_after=15, ephemeral=True)
|
await ctx.respond('❌ У вас нет треков в плейлисте «Мне нравится».', delete_after=15, ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
real_tracks = await gather(*[track_short.fetch_track_async() for track_short in likes.tracks], return_exceptions=True)
|
real_tracks = await gather(*[track_short.fetch_track_async() for track_short in likes.tracks], return_exceptions=True)
|
||||||
tracks = [track for track in real_tracks if not isinstance(track, BaseException)] # Can't fetch user tracks
|
tracks = [track for track in real_tracks if not isinstance(track, BaseException)] # Can't fetch user tracks
|
||||||
embed = await generate_item_embed(tracks)
|
|
||||||
logging.info(f"[GENERAL] Successfully fetched likes for user {ctx.user.id}")
|
await ctx.respond(embed=await generate_item_embed(tracks), view=ListenView(tracks))
|
||||||
await ctx.respond(embed=embed, view=ListenView(tracks))
|
logging.info(f"[GENERAL] Successfully generated likes message for user {ctx.user.id}")
|
||||||
|
|
||||||
@account.command(description="Получить ваши рекомендации.")
|
@account.command(description="Получить ваши рекомендации.")
|
||||||
@discord.option(
|
@discord.option(
|
||||||
@@ -263,15 +258,19 @@ class General(Cog):
|
|||||||
# NOTE: Recommendations can be accessed by using /find, but it's more convenient to have it in separate command.
|
# NOTE: Recommendations can be accessed by using /find, but it's more convenient to have it in separate command.
|
||||||
logging.debug(f"[GENERAL] Recommendations command invoked by user {ctx.user.id} in guild {ctx.guild_id} for type '{content_type}'")
|
logging.debug(f"[GENERAL] Recommendations command invoked by user {ctx.user.id} in guild {ctx.guild_id} for type '{content_type}'")
|
||||||
|
|
||||||
guild = await self.db.get_guild(ctx.guild_id)
|
|
||||||
token = await self.users_db.get_ym_token(ctx.user.id)
|
token = await self.users_db.get_ym_token(ctx.user.id)
|
||||||
if not token:
|
if not token:
|
||||||
await ctx.respond("❌ Укажите токен через /account login.", delete_after=15, ephemeral=True)
|
await ctx.respond("❌ Укажите токен через /account login.", delete_after=15, ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
client = await YMClient(token).init()
|
try:
|
||||||
|
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)
|
||||||
|
return
|
||||||
|
|
||||||
search = await client.search(content_type, False, 'playlist')
|
search = await client.search(content_type, type_='playlist')
|
||||||
if not search or not search.playlists:
|
if not search or not search.playlists:
|
||||||
logging.info(f"[GENERAL] Failed to fetch recommendations for user {ctx.user.id}")
|
logging.info(f"[GENERAL] Failed to fetch recommendations for user {ctx.user.id}")
|
||||||
await ctx.respond('❌ Что-то пошло не так. Повторите попытку позже.', delete_after=15, ephemeral=True)
|
await ctx.respond('❌ Что-то пошло не так. Повторите попытку позже.', delete_after=15, ephemeral=True)
|
||||||
@@ -288,18 +287,7 @@ class General(Cog):
|
|||||||
await ctx.respond("❌ Пустой плейлист.", delete_after=15, ephemeral=True)
|
await ctx.respond("❌ Пустой плейлист.", delete_after=15, ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
embed = await generate_item_embed(playlist)
|
await ctx.respond(embed=await generate_item_embed(playlist), view=ListenView(playlist))
|
||||||
view = ListenView(playlist)
|
|
||||||
|
|
||||||
for track_short in playlist.tracks:
|
|
||||||
track = cast(Track, track_short.track)
|
|
||||||
if (track.explicit or track.content_warning) and not guild['allow_explicit']:
|
|
||||||
logging.info(f"[GENERAL] User {ctx.user.id} search for '{content_type}' returned explicit content and is not allowed on this server")
|
|
||||||
embed.set_footer(text="Воспроизведение недоступно, так как в плейлисте присутствуют Explicit треки")
|
|
||||||
view = None
|
|
||||||
break
|
|
||||||
|
|
||||||
await ctx.respond(embed=embed, view=view)
|
|
||||||
|
|
||||||
@account.command(description="Получить ваш плейлист.")
|
@account.command(description="Получить ваш плейлист.")
|
||||||
@discord.option(
|
@discord.option(
|
||||||
@@ -312,7 +300,6 @@ class General(Cog):
|
|||||||
async def playlist(self, ctx: discord.ApplicationContext, name: str) -> None:
|
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] Playlists command invoked by user {ctx.user.id} in guild {ctx.guild_id}")
|
||||||
|
|
||||||
guild = await self.db.get_guild(ctx.guild_id, projection={'allow_explicit': 1})
|
|
||||||
token = await self.users_db.get_ym_token(ctx.user.id)
|
token = await 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}")
|
||||||
@@ -321,7 +308,7 @@ class General(Cog):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
client = await YMClient(token).init()
|
client = await YMClient(token).init()
|
||||||
except yandex_music.exceptions.UnauthorizedError:
|
except UnauthorizedError:
|
||||||
logging.info(f"[GENERAL] User {ctx.user.id} provided invalid token")
|
logging.info(f"[GENERAL] User {ctx.user.id} provided invalid token")
|
||||||
await ctx.respond("❌ Недействительный токен. Если это не так, попробуйте ещё раз.", delete_after=15, ephemeral=True)
|
await ctx.respond("❌ Недействительный токен. Если это не так, попробуйте ещё раз.", delete_after=15, ephemeral=True)
|
||||||
return
|
return
|
||||||
@@ -339,19 +326,8 @@ class General(Cog):
|
|||||||
logging.info(f"[GENERAL] User {ctx.user.id} playlist '{name}' is empty")
|
logging.info(f"[GENERAL] User {ctx.user.id} playlist '{name}' is empty")
|
||||||
await ctx.respond("❌ Плейлист пуст.", delete_after=15, ephemeral=True)
|
await ctx.respond("❌ Плейлист пуст.", delete_after=15, ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
embed = await generate_item_embed(playlist)
|
|
||||||
view = ListenView(playlist)
|
|
||||||
|
|
||||||
for track_short in playlist.tracks:
|
|
||||||
track = cast(Track, track_short.track)
|
|
||||||
if (track.explicit or track.content_warning) and not guild['allow_explicit']:
|
|
||||||
logging.info(f"[GENERAL] User {ctx.user.id} search for '{name}' returned explicit content and is not allowed on this server")
|
|
||||||
embed.set_footer(text="Воспроизведение недоступно, так как в плейлисте присутствуют Explicit треки")
|
|
||||||
view = None
|
|
||||||
break
|
|
||||||
|
|
||||||
await ctx.respond(embed=embed, view=view)
|
await ctx.respond(embed=await generate_item_embed(playlist), view=ListenView(playlist))
|
||||||
|
|
||||||
@discord.slash_command(description="Найти контент и отправить информацию о нём. Возвращается лучшее совпадение.")
|
@discord.slash_command(description="Найти контент и отправить информацию о нём. Возвращается лучшее совпадение.")
|
||||||
@discord.option(
|
@discord.option(
|
||||||
@@ -374,11 +350,8 @@ class General(Cog):
|
|||||||
content_type: Literal['Трек', 'Альбом', 'Артист', 'Плейлист'],
|
content_type: Literal['Трек', 'Альбом', 'Артист', 'Плейлист'],
|
||||||
name: str
|
name: str
|
||||||
) -> None:
|
) -> None:
|
||||||
# TODO: Improve explicit check by excluding bad tracks from the queue and not fully discard the artist/album/playlist.
|
|
||||||
|
|
||||||
logging.info(f"[GENERAL] Find command invoked by user {ctx.user.id} in guild {ctx.guild_id} for '{content_type}' with name '{name}'")
|
logging.info(f"[GENERAL] Find command invoked by user {ctx.user.id} in guild {ctx.guild_id} for '{content_type}' with name '{name}'")
|
||||||
|
|
||||||
guild = await self.db.get_guild(ctx.guild_id, projection={'allow_explicit': 1})
|
|
||||||
token = await self.users_db.get_ym_token(ctx.user.id)
|
token = await 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}")
|
||||||
@@ -387,66 +360,32 @@ class General(Cog):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
client = await YMClient(token).init()
|
client = await YMClient(token).init()
|
||||||
except yandex_music.exceptions.UnauthorizedError:
|
except UnauthorizedError:
|
||||||
logging.info(f"[GENERAL] User {ctx.user.id} provided invalid token")
|
logging.info(f"[GENERAL] User {ctx.user.id} provided invalid token")
|
||||||
await ctx.respond("❌ Недействительный токен. Если это не так, попробуйте ещё раз.", delete_after=15, ephemeral=True)
|
await ctx.respond("❌ Недействительный токен. Если это не так, попробуйте ещё раз.", delete_after=15, ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
result = await client.search(name, nocorrect=True)
|
search_result = await client.search(name, nocorrect=True)
|
||||||
|
if not search_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}")
|
||||||
await ctx.respond("❌ Что-то пошло не так. Повторите попытку позже.", delete_after=15, ephemeral=True)
|
await ctx.respond("❌ Что-то пошло не так. Повторите попытку позже.", delete_after=15, ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
if content_type == 'Трек':
|
if content_type == 'Трек':
|
||||||
content = result.tracks
|
content = search_result.tracks
|
||||||
elif content_type == 'Альбом':
|
elif content_type == 'Альбом':
|
||||||
content = result.albums
|
content = search_result.albums
|
||||||
elif content_type == 'Артист':
|
elif content_type == 'Артист':
|
||||||
content = result.artists
|
content = search_result.artists
|
||||||
elif content_type == 'Плейлист':
|
else:
|
||||||
content = result.playlists
|
content = search_result.playlists
|
||||||
|
|
||||||
if not content:
|
if not content:
|
||||||
logging.info(f"[GENERAL] User {ctx.user.id} search for '{name}' returned no results")
|
logging.info(f"[GENERAL] User {ctx.user.id} search for '{name}' returned no results")
|
||||||
await ctx.respond("❌ По запросу ничего не найдено.", delete_after=15, ephemeral=True)
|
await ctx.respond("❌ По запросу ничего не найдено.", delete_after=15, ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
content = content.results[0]
|
result = content.results[0]
|
||||||
embed = await generate_item_embed(content)
|
await ctx.respond(embed=await generate_item_embed(result), view=ListenView(result))
|
||||||
view = ListenView(content)
|
|
||||||
|
|
||||||
if isinstance(content, (Track, Album)) and (content.explicit or content.content_warning) and not guild['allow_explicit']:
|
|
||||||
logging.info(f"[GENERAL] User {ctx.user.id} search for '{name}' returned explicit content and is not allowed on this server")
|
|
||||||
await ctx.respond("❌ Explicit контент запрещён на этом сервере.", delete_after=15, ephemeral=True)
|
|
||||||
return
|
|
||||||
elif isinstance(content, Artist):
|
|
||||||
tracks = await content.get_tracks_async()
|
|
||||||
if not tracks:
|
|
||||||
logging.info(f"[GENERAL] User {ctx.user.id} search for '{name}' returned no tracks")
|
|
||||||
await ctx.respond("❌ Треки от этого исполнителя не найдены.", delete_after=15, ephemeral=True)
|
|
||||||
return
|
|
||||||
for track in tracks:
|
|
||||||
if (track.explicit or track.content_warning) and not guild['allow_explicit']:
|
|
||||||
logging.info(f"[GENERAL] User {ctx.user.id} search for '{name}' returned explicit content and is not allowed on this server")
|
|
||||||
view = None
|
|
||||||
embed.set_footer(text="Воспроизведение недоступно, так как у автора присутствуют Explicit треки")
|
|
||||||
break
|
|
||||||
|
|
||||||
elif isinstance(content, Playlist):
|
|
||||||
tracks = await content.fetch_tracks_async()
|
|
||||||
if not tracks:
|
|
||||||
logging.info(f"[GENERAL] User {ctx.user.id} search for '{name}' returned no tracks")
|
|
||||||
await ctx.respond("❌ Пустой плейлист.", delete_after=15, ephemeral=True)
|
|
||||||
return
|
|
||||||
for track_short in content.tracks:
|
|
||||||
track = cast(Track, track_short.track)
|
|
||||||
if (track.explicit or track.content_warning) and not guild['allow_explicit']:
|
|
||||||
logging.info(f"[GENERAL] User {ctx.user.id} search for '{name}' returned explicit content and is not allowed on this server")
|
|
||||||
view = None
|
|
||||||
embed.set_footer(text="Воспроизведение недоступно, так как в плейлисте присутствуют Explicit треки")
|
|
||||||
break
|
|
||||||
|
|
||||||
logging.info(f"[GENERAL] 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)
|
|
||||||
|
|||||||
@@ -20,13 +20,11 @@ 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,
|
'always_allow_menu': 1, 'allow_connect': 1, 'allow_disconnect': 1,
|
||||||
'vote_next_track': 1, 'vote_add_track': 1, 'vote_add_album': 1, 'vote_add_artist': 1, 'vote_add_playlist': 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 "❌ - Запрещены"
|
|
||||||
menu = "✅ - Всегда доступно" if guild['always_allow_menu'] else "❌ - Если в канале 1 человек."
|
menu = "✅ - Всегда доступно" if guild['always_allow_menu'] else "❌ - Если в канале 1 человек."
|
||||||
|
|
||||||
vote = "✅ - Переключение" if guild['vote_next_track'] else "❌ - Переключение"
|
vote = "✅ - Переключение" if guild['vote_next_track'] else "❌ - Переключение"
|
||||||
@@ -37,10 +35,9 @@ class Settings(Cog):
|
|||||||
|
|
||||||
connect = "\n✅ - Разрешено всем" if guild['allow_connect'] else "\n❌ - Только для участникам с правами управления каналом"
|
connect = "\n✅ - Разрешено всем" if guild['allow_connect'] else "\n❌ - Только для участникам с правами управления каналом"
|
||||||
|
|
||||||
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)
|
embed.add_field(name="__Подключение/Отключение__", value=connect, inline=False)
|
||||||
|
|
||||||
await ctx.respond(embed=embed, ephemeral=True)
|
await ctx.respond(embed=embed, ephemeral=True)
|
||||||
|
|
||||||
@@ -54,17 +51,6 @@ class Settings(Cog):
|
|||||||
guild = await self.db.get_guild(ctx.guild.id, projection={'allow_connect': 1})
|
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 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)
|
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:
|
|
||||||
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_explicit': 1})
|
|
||||||
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)
|
|
||||||
|
|
||||||
@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:
|
||||||
|
|||||||
@@ -47,25 +47,22 @@ class Voice(Cog, VoiceExtension):
|
|||||||
|
|
||||||
@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:
|
||||||
logging.info(f"[VOICE] Voice state update for member {member.id} in guild {member.guild.id}")
|
|
||||||
|
|
||||||
gid = member.guild.id
|
gid = member.guild.id
|
||||||
guild = await self.db.get_guild(gid, projection={'current_menu': 1, 'always_allow_menu': 1})
|
guild = await self.db.get_guild(gid, projection={'current_menu': 1, 'always_allow_menu': 1})
|
||||||
discord_guild = await self.typed_bot.fetch_guild(gid)
|
|
||||||
current_menu = guild['current_menu']
|
|
||||||
|
|
||||||
channel = after.channel or before.channel
|
channel = after.channel or before.channel
|
||||||
if not channel:
|
if not channel:
|
||||||
logging.warning(f"[VOICE] No channel found for member {member.id}")
|
logging.warning(f"[VOICE] No channel found for member {member.id}")
|
||||||
return
|
return
|
||||||
|
|
||||||
vc = cast(discord.VoiceClient | None, discord.utils.get(self.typed_bot.voice_clients, guild=discord_guild))
|
vc = cast(discord.VoiceClient | None, discord.utils.get(self.typed_bot.voice_clients, guild=await self.typed_bot.fetch_guild(gid)))
|
||||||
|
|
||||||
for member in channel.members:
|
for member in channel.members:
|
||||||
if member.id == self.typed_bot.user.id: # type: ignore # should be logged in
|
if member.id == self.typed_bot.user.id: # type: ignore # should be logged in
|
||||||
|
logging.info(f"[VOICE] Voice state update for member {member.id} in guild {member.guild.id}")
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
logging.info(f"[VOICE] Bot is not in the channel {channel.id}")
|
logging.debug(f"[VOICE] Bot is not in the channel {channel.id}")
|
||||||
return
|
return
|
||||||
|
|
||||||
if not vc:
|
if not vc:
|
||||||
@@ -91,12 +88,12 @@ class Voice(Cog, VoiceExtension):
|
|||||||
})
|
})
|
||||||
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 guild['current_menu']:
|
||||||
logging.info(f"[VOICE] Disabling current menu for guild {gid} due to multiple members")
|
logging.info(f"[VOICE] Disabling current menu for guild {gid} due to multiple members")
|
||||||
|
|
||||||
await self.db.update(gid, {'current_menu': None, 'repeat': False, 'shuffle': False, 'vibing': False})
|
await self.db.update(gid, {'current_menu': None, 'repeat': False, 'shuffle': False, 'vibing': False})
|
||||||
try:
|
try:
|
||||||
message = await channel.fetch_message(current_menu)
|
message = await channel.fetch_message(guild['current_menu'])
|
||||||
await message.delete()
|
await message.delete()
|
||||||
await channel.send("Меню отключено из-за большого количества участников.", delete_after=15)
|
await channel.send("Меню отключено из-за большого количества участников.", delete_after=15)
|
||||||
except (discord.NotFound, discord.Forbidden):
|
except (discord.NotFound, discord.Forbidden):
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ class BaseGuildsDatabase:
|
|||||||
current_track=None,
|
current_track=None,
|
||||||
current_menu=None,
|
current_menu=None,
|
||||||
is_stopped=True,
|
is_stopped=True,
|
||||||
allow_explicit=True,
|
|
||||||
always_allow_menu=False,
|
always_allow_menu=False,
|
||||||
allow_connect=True,
|
allow_connect=True,
|
||||||
vote_next_track=True,
|
vote_next_track=True,
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ class Guild(TypedDict, total=False):
|
|||||||
current_track: dict[str, Any] | None
|
current_track: dict[str, Any] | None
|
||||||
current_menu: int | None
|
current_menu: int | None
|
||||||
is_stopped: bool
|
is_stopped: bool
|
||||||
allow_explicit: bool
|
|
||||||
always_allow_menu: bool
|
always_allow_menu: bool
|
||||||
allow_connect: bool
|
allow_connect: bool
|
||||||
vote_next_track: bool
|
vote_next_track: bool
|
||||||
@@ -34,7 +33,6 @@ class ExplicitGuild(TypedDict):
|
|||||||
current_track: dict[str, Any] | None
|
current_track: dict[str, Any] | None
|
||||||
current_menu: int | None
|
current_menu: int | None
|
||||||
is_stopped: bool # Prevents the `after` callback of play_track
|
is_stopped: bool # Prevents the `after` callback of play_track
|
||||||
allow_explicit: bool
|
|
||||||
always_allow_menu: bool
|
always_allow_menu: bool
|
||||||
allow_connect: bool
|
allow_connect: bool
|
||||||
vote_next_track: bool
|
vote_next_track: bool
|
||||||
@@ -46,4 +44,4 @@ class ExplicitGuild(TypedDict):
|
|||||||
repeat: bool
|
repeat: bool
|
||||||
votes: dict[str, MessageVotes]
|
votes: dict[str, MessageVotes]
|
||||||
vibing: bool
|
vibing: bool
|
||||||
current_viber_id: int | None
|
current_viber_id: int | None
|
||||||
|
|||||||
Reference in New Issue
Block a user