impr: Remove explicit checks due to poor support and uselessness.

This commit is contained in:
Lemon4ksan
2025-02-18 17:39:14 +03:00
parent c042f2d86f
commit 617a7a6de9
5 changed files with 80 additions and 161 deletions

View File

@@ -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)

View File

@@ -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:

View File

@@ -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):

View File

@@ -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,

View File

@@ -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