mirror of
https://github.com/deadcxap/YandexMusicDiscordBot.git
synced 2026-01-11 02:21:44 +03:00
feat: Add station search for /voice vibe.
This commit is contained in:
@@ -57,11 +57,8 @@ async def get_search_suggestions(ctx: discord.AutocompleteContext) -> list[str]:
|
|||||||
for item in search.playlists.results:
|
for item in search.playlists.results:
|
||||||
res.append(f"{item.title}")
|
res.append(f"{item.title}")
|
||||||
elif content_type == "Свой плейлист":
|
elif content_type == "Свой плейлист":
|
||||||
if not client.me or not client.me.account or not client.me.account.uid:
|
playlists_list = await client.users_playlists_list()
|
||||||
logging.warning(f"Failed to get playlists for user {ctx.interaction.user.id}")
|
res = [playlist.title if playlist.title else 'Без названия' for playlist in playlists_list]
|
||||||
else:
|
|
||||||
playlists_list = await client.users_playlists_list(client.me.account.uid)
|
|
||||||
res = [playlist.title if playlist.title else 'Без названия' for playlist in playlists_list]
|
|
||||||
|
|
||||||
return res[:100]
|
return res[:100]
|
||||||
|
|
||||||
@@ -81,7 +78,7 @@ async def get_user_playlists_suggestions(ctx: discord.AutocompleteContext) -> li
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
playlists_list = await client.users_playlists_list()
|
playlists_list = await client.users_playlists_list()
|
||||||
return [playlist.title if playlist.title else 'Без названия' for playlist in playlists_list]
|
return [playlist.title for playlist in playlists_list if playlist.title and ctx.value in playlist.title][:100]
|
||||||
|
|
||||||
class General(Cog):
|
class General(Cog):
|
||||||
|
|
||||||
@@ -159,12 +156,12 @@ class General(Cog):
|
|||||||
)
|
)
|
||||||
elif command == 'settings':
|
elif command == 'settings':
|
||||||
embed.description += (
|
embed.description += (
|
||||||
|
"`Примечание`: Только пользователи с разрешением управления каналом могут менять настройки.\n\n"
|
||||||
"Получить текущие настройки.\n```/settings show```\n"
|
"Получить текущие настройки.\n```/settings show```\n"
|
||||||
"Разрешить или запретить воспроизведение Explicit треков и альбомов. Если автор или плейлист содержат Explicit треки, убираются кнопки для доступа к ним.\n```/settings explicit```\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"
|
||||||
"`Примечание`: Только пользователи с разрешением управления каналом могут менять настройки."
|
|
||||||
)
|
)
|
||||||
elif command == 'track':
|
elif command == 'track':
|
||||||
embed.description += (
|
embed.description += (
|
||||||
@@ -179,10 +176,10 @@ class General(Cog):
|
|||||||
elif command == 'voice':
|
elif command == 'voice':
|
||||||
embed.description += (
|
embed.description += (
|
||||||
"`Примечание`: Доступность меню и Моей Волны зависит от настроек сервера.\n\n"
|
"`Примечание`: Доступность меню и Моей Волны зависит от настроек сервера.\n\n"
|
||||||
"Присоединить бота в голосовой канал. Требует разрешения управления каналом.\n```/voice join```\n"
|
"Присоединить бота в голосовой канал.\n```/voice join```\n"
|
||||||
"Заставить бота покинуть голосовой канал. Требует разрешения управления каналом.\n ```/voice leave```\n"
|
"Заставить бота покинуть голосовой канал.\n ```/voice leave```\n"
|
||||||
"Создать меню проигрывателя. По умолчанию работает только когда в канале один человек.\n```/voice menu```\n"
|
"Создать меню проигрывателя. \n```/voice menu```\n"
|
||||||
"Запустить Мою Волну. По умолчанию работает только когда в канале один человек.\n```/vibe```"
|
"Запустить станцию. Без уточнения станции, запускает Мою Волну.\n```/voice vibe <название станции>```"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
response_message = '❌ Неизвестная команда.'
|
response_message = '❌ Неизвестная команда.'
|
||||||
@@ -410,8 +407,8 @@ class General(Cog):
|
|||||||
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]
|
|
||||||
|
|
||||||
|
content = content.results[0]
|
||||||
embed = await generate_item_embed(content)
|
embed = await generate_item_embed(content)
|
||||||
view = ListenView(content)
|
view = ListenView(content)
|
||||||
|
|
||||||
@@ -431,6 +428,7 @@ class General(Cog):
|
|||||||
view = None
|
view = None
|
||||||
embed.set_footer(text="Воспроизведение недоступно, так как у автора присутствуют Explicit треки")
|
embed.set_footer(text="Воспроизведение недоступно, так как у автора присутствуют Explicit треки")
|
||||||
break
|
break
|
||||||
|
|
||||||
elif isinstance(content, Playlist):
|
elif isinstance(content, Playlist):
|
||||||
tracks = await content.fetch_tracks_async()
|
tracks = await content.fetch_tracks_async()
|
||||||
if not tracks:
|
if not tracks:
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ class VoiceExtension:
|
|||||||
async def update_vibe(
|
async def update_vibe(
|
||||||
self,
|
self,
|
||||||
ctx: ApplicationContext | Interaction,
|
ctx: ApplicationContext | Interaction,
|
||||||
type: Literal['track', 'album', 'artist', 'playlist', 'user'],
|
type: str,
|
||||||
id: str | int,
|
id: str | int,
|
||||||
*,
|
*,
|
||||||
update_settings: bool = False
|
update_settings: bool = False
|
||||||
@@ -241,8 +241,8 @@ class VoiceExtension:
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
ctx (ApplicationContext | Interaction): Context.
|
ctx (ApplicationContext | Interaction): Context.
|
||||||
type (Literal['track', 'album', 'artist', 'playlist', 'user']): Type of the item.
|
type (str): Type of the item.
|
||||||
id (str | int): ID of the YandexMusic item.
|
id (str | int): ID of the item.
|
||||||
update_settings (bool, optional): Update vibe settings by sending feedack usind data from database. Defaults to False.
|
update_settings (bool, optional): Update vibe settings by sending feedack usind data from database. Defaults to False.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|||||||
@@ -4,12 +4,37 @@ from typing import cast
|
|||||||
import discord
|
import discord
|
||||||
from discord.ext.commands import Cog
|
from discord.ext.commands import Cog
|
||||||
|
|
||||||
|
from yandex_music import ClientAsync as YMClient
|
||||||
|
from yandex_music.exceptions import UnauthorizedError
|
||||||
|
|
||||||
|
from MusicBot.database import BaseUsersDatabase
|
||||||
from MusicBot.cogs.utils import VoiceExtension, menu_views
|
from MusicBot.cogs.utils import VoiceExtension, menu_views
|
||||||
from MusicBot.ui import QueueView, generate_queue_embed
|
from MusicBot.ui import QueueView, generate_queue_embed
|
||||||
|
|
||||||
def setup(bot: discord.Bot):
|
def setup(bot: discord.Bot):
|
||||||
bot.add_cog(Voice(bot))
|
bot.add_cog(Voice(bot))
|
||||||
|
|
||||||
|
users_db = BaseUsersDatabase()
|
||||||
|
|
||||||
|
async def get_vibe_stations_suggestions(ctx: discord.AutocompleteContext) -> list[str]:
|
||||||
|
if not ctx.interaction.user or not ctx.value or len(ctx.value) < 2:
|
||||||
|
return []
|
||||||
|
|
||||||
|
token = await users_db.get_ym_token(ctx.interaction.user.id)
|
||||||
|
if not token:
|
||||||
|
logging.info(f"[GENERAL] User {ctx.interaction.user.id} has no token")
|
||||||
|
return []
|
||||||
|
|
||||||
|
try:
|
||||||
|
client = await YMClient(token).init()
|
||||||
|
except UnauthorizedError:
|
||||||
|
logging.info(f"[GENERAL] User {ctx.interaction.user.id} provided invalid token")
|
||||||
|
return []
|
||||||
|
|
||||||
|
stations = await client.rotor_stations_list()
|
||||||
|
return [station.station.name for station in stations if station.station and ctx.value in station.station.name][:100]
|
||||||
|
|
||||||
|
|
||||||
class Voice(Cog, VoiceExtension):
|
class Voice(Cog, VoiceExtension):
|
||||||
|
|
||||||
voice = discord.SlashCommandGroup("voice", "Команды, связанные с голосовым каналом.")
|
voice = discord.SlashCommandGroup("voice", "Команды, связанные с голосовым каналом.")
|
||||||
@@ -436,13 +461,19 @@ class Voice(Cog, VoiceExtension):
|
|||||||
if not await self.voice_check(ctx):
|
if not await self.voice_check(ctx):
|
||||||
return
|
return
|
||||||
|
|
||||||
guild = await self.db.get_guild(ctx.guild.id, projection={'always_allow_menu': 1, 'current_track': 1, 'current_menu': 1})
|
guild = await self.db.get_guild(ctx.guild.id, projection={'always_allow_menu': 1, 'current_track': 1, 'current_menu': 1, 'vibing': 1})
|
||||||
channel = cast(discord.VoiceChannel, ctx.channel)
|
channel = cast(discord.VoiceChannel, ctx.channel)
|
||||||
|
|
||||||
if len(channel.members) > 2 and not guild['always_allow_menu']:
|
if len(channel.members) > 2 and not guild['always_allow_menu']:
|
||||||
logging.info(f"[VOICE] Action declined: other members are present in the voice channel")
|
logging.info(f"[VOICE] Action declined: other members are present in the voice channel")
|
||||||
await ctx.respond("❌ Вы не единственный в голосовом канале.", ephemeral=True)
|
await ctx.respond("❌ Вы не единственный в голосовом канале.", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if guild['vibing']:
|
||||||
|
logging.info(f"[VOICE] Action declined: vibing is already enabled in guild {ctx.guild.id}")
|
||||||
|
await ctx.respond("❌ Моя Волна уже включена. Используйте /track stop, чтобы остановить воспроизведение.", ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
if not guild['current_track']:
|
if not guild['current_track']:
|
||||||
logging.info(f"[VOICE] No current track in {ctx.guild.id}")
|
logging.info(f"[VOICE] No current track in {ctx.guild.id}")
|
||||||
await ctx.respond("❌ Нет воспроизводимого трека.", ephemeral=True)
|
await ctx.respond("❌ Нет воспроизводимого трека.", ephemeral=True)
|
||||||
@@ -452,7 +483,7 @@ class Voice(Cog, VoiceExtension):
|
|||||||
if not feedback:
|
if not feedback:
|
||||||
await ctx.respond("❌ Операция не удалась. Возможно, у вес нет подписки на Яндекс Музыку.", ephemeral=True)
|
await ctx.respond("❌ Операция не удалась. Возможно, у вес нет подписки на Яндекс Музыку.", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not guild['current_menu']:
|
if not guild['current_menu']:
|
||||||
await self.send_menu_message(ctx, disable=True)
|
await self.send_menu_message(ctx, disable=True)
|
||||||
|
|
||||||
@@ -461,24 +492,70 @@ class Voice(Cog, VoiceExtension):
|
|||||||
await self._play_next_track(ctx, next_track)
|
await self._play_next_track(ctx, next_track)
|
||||||
|
|
||||||
@voice.command(name='vibe', description="Запустить Мою Волну.")
|
@voice.command(name='vibe', description="Запустить Мою Волну.")
|
||||||
async def user_vibe(self, ctx: discord.ApplicationContext) -> None:
|
@discord.option(
|
||||||
|
"запрос",
|
||||||
|
parameter_name='name',
|
||||||
|
description="Название станции.",
|
||||||
|
type=discord.SlashCommandOptionType.string,
|
||||||
|
autocomplete=discord.utils.basic_autocomplete(get_vibe_stations_suggestions),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
async def user_vibe(self, ctx: discord.ApplicationContext, name: str | None = None) -> None:
|
||||||
logging.info(f"[VOICE] Vibe (user) command invoked by user {ctx.user.id} in guild {ctx.guild_id}")
|
logging.info(f"[VOICE] Vibe (user) command invoked by user {ctx.user.id} in guild {ctx.guild_id}")
|
||||||
if not await self.voice_check(ctx):
|
if not await self.voice_check(ctx):
|
||||||
return
|
return
|
||||||
|
|
||||||
guild = await self.db.get_guild(ctx.guild.id, projection={'always_allow_menu': 1, 'current_menu': 1})
|
guild = await self.db.get_guild(ctx.guild.id, projection={'always_allow_menu': 1, 'current_menu': 1, 'vibing': 1})
|
||||||
channel = cast(discord.VoiceChannel, ctx.channel)
|
channel = cast(discord.VoiceChannel, ctx.channel)
|
||||||
|
|
||||||
if len(channel.members) > 2 and not guild['always_allow_menu']:
|
if len(channel.members) > 2 and not guild['always_allow_menu']:
|
||||||
logging.info(f"[VOICE] Action declined: other members are present in the voice channel")
|
logging.info(f"[VOICE] Action declined: other members are present in the voice channel")
|
||||||
await ctx.respond("❌ Вы не единственный в голосовом канале.", ephemeral=True)
|
await ctx.respond("❌ Вы не единственный в голосовом канале.", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
if guild['vibing']:
|
||||||
|
logging.info(f"[VOICE] Action declined: vibing is already enabled in guild {ctx.guild.id}")
|
||||||
|
await ctx.respond("❌ Моя Волна уже включена. Используйте /track stop, чтобы остановить воспроизведение.", ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
if name:
|
||||||
|
token = await users_db.get_ym_token(ctx.user.id)
|
||||||
|
if not token:
|
||||||
|
logging.info(f"[GENERAL] User {ctx.user.id} has no token")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
client = await YMClient(token).init()
|
||||||
|
except UnauthorizedError:
|
||||||
|
logging.info(f"[GENERAL] User {ctx.user.id} provided invalid token")
|
||||||
|
return
|
||||||
|
|
||||||
|
stations = await client.rotor_stations_list()
|
||||||
|
for content in stations:
|
||||||
|
if content.station and content.station.name == name and content.ad_params:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
content = None
|
||||||
|
|
||||||
|
if not content:
|
||||||
|
logging.debug(f"[VOICE] Station {name} not found")
|
||||||
|
await ctx.respond("❌ Станция не найдена.", ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
_type, _id = content.ad_params.other_params.split(':') if content.ad_params else (None, None)
|
||||||
|
|
||||||
|
if not _type or not _id:
|
||||||
|
logging.debug(f"[VOICE] Station {name} has no ad params")
|
||||||
|
await ctx.respond("❌ Станция не найдена.", ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
feedback = await self.update_vibe(ctx, _type, _id)
|
||||||
|
else:
|
||||||
|
feedback = await self.update_vibe(ctx, 'user', 'onyourwave')
|
||||||
|
|
||||||
feedback = await self.update_vibe(ctx, 'user', 'onyourwave')
|
|
||||||
if not feedback:
|
if not feedback:
|
||||||
await ctx.respond("❌ Операция не удалась. Возможно, у вес нет подписки на Яндекс Музыку.", ephemeral=True)
|
await ctx.respond("❌ Операция не удалась. Возможно, у вес нет подписки на Яндекс Музыку.", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not guild['current_menu']:
|
if not guild['current_menu']:
|
||||||
await self.send_menu_message(ctx, disable=True)
|
await self.send_menu_message(ctx, disable=True)
|
||||||
|
|
||||||
|
|||||||
@@ -17,17 +17,18 @@ cogs_list = [
|
|||||||
@bot.event
|
@bot.event
|
||||||
async def on_ready():
|
async def on_ready():
|
||||||
logging.info("Bot's ready!")
|
logging.info("Bot's ready!")
|
||||||
|
await bot.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name="/voice vibe"))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import coloredlogs
|
import coloredlogs
|
||||||
coloredlogs.install(level=logging.DEBUG)
|
coloredlogs.install(level=logging.DEBUG)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if os.getenv('DEBUG') == 'True':
|
if os.getenv('DEBUG') == 'True':
|
||||||
logging.getLogger().setLevel(logging.DEBUG)
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
logging.getLogger('discord').setLevel(logging.INFO)
|
logging.getLogger('discord').setLevel(logging.INFO)
|
||||||
@@ -38,14 +39,14 @@ if __name__ == '__main__':
|
|||||||
logging.getLogger('discord').setLevel(logging.WARNING)
|
logging.getLogger('discord').setLevel(logging.WARNING)
|
||||||
logging.getLogger('pymongo').setLevel(logging.WARNING)
|
logging.getLogger('pymongo').setLevel(logging.WARNING)
|
||||||
logging.getLogger('yandex_music').setLevel(logging.WARNING)
|
logging.getLogger('yandex_music').setLevel(logging.WARNING)
|
||||||
|
|
||||||
if not os.path.exists('music'):
|
if not os.path.exists('music'):
|
||||||
os.mkdir('music')
|
os.mkdir('music')
|
||||||
token = os.getenv('TOKEN')
|
token = os.getenv('TOKEN')
|
||||||
if not token:
|
if not token:
|
||||||
raise ValueError('You must specify the bot TOKEN in your enviroment')
|
raise ValueError('You must specify the bot TOKEN in your enviroment')
|
||||||
|
|
||||||
for cog in cogs_list:
|
for cog in cogs_list:
|
||||||
bot.load_extension(f'MusicBot.cogs.{cog}')
|
bot.load_extension(f'MusicBot.cogs.{cog}')
|
||||||
|
|
||||||
bot.run(token)
|
bot.run(token)
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ class MyVibeButton(Button, VoiceExtension):
|
|||||||
await interaction.respond("❌ Вы не единственный в голосовом канале.", ephemeral=True)
|
await interaction.respond("❌ Вы не единственный в голосовом канале.", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
track_type_map: dict[Any, Literal['track', 'album', 'artist', 'playlist', 'user']] = {
|
track_type_map = {
|
||||||
Track: 'track', Album: 'album', Artist: 'artist', Playlist: 'playlist', list: 'user'
|
Track: 'track', Album: 'album', Artist: 'artist', Playlist: 'playlist', list: 'user'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +211,7 @@ class ListenView(View):
|
|||||||
self.add_item(self.button2)
|
self.add_item(self.button2)
|
||||||
self.add_item(self.button3)
|
self.add_item(self.button3)
|
||||||
self.add_item(self.button4)
|
self.add_item(self.button4)
|
||||||
|
|
||||||
async def on_timeout(self) -> None:
|
async def on_timeout(self) -> None:
|
||||||
try:
|
try:
|
||||||
return await super().on_timeout()
|
return await super().on_timeout()
|
||||||
|
|||||||
Reference in New Issue
Block a user