feat: Add to playlist and dislike button.

This commit is contained in:
Lemon4ksan
2025-02-01 22:20:22 +03:00
parent 956dc4d925
commit a74bf152c0
5 changed files with 214 additions and 62 deletions

View File

@@ -19,7 +19,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:
if not ctx.interaction.user or not ctx.value or len(ctx.value) < 2:
return []
users_db = BaseUsersDatabase()
@@ -93,10 +93,11 @@ class General(Cog):
if command == 'all':
embed.description = (
"Этот бот позволяет слушать музыку из вашего аккаунта Yandex Music.\n"
"Этот бот позволяет слушать музыку из вашего аккаунта Яндекс Музыки.\n"
"Зарегистрируйте свой токен с помощью /login. Его можно получить [здесь](https://github.com/MarshalX/yandex-music-api/discussions/513).\n"
"Для получения помощи по конкретной команде, введите /help <команда>.\n"
"Для изменения настроек необходимо иметь права управления каналами на сервере.\n\n"
"Помните, что это **не замена Яндекс Музыки**, а лишь её дополнение. Не ожидайте безупречного звука.\n\n"
"**Для дополнительной помощи, присоединяйтесь к [серверу любителей Яндекс Музыки](https://discord.gg/gkmFDaPMeC).**"
)

View File

@@ -1,5 +1,7 @@
import asyncio
import aiofiles
import logging
import io
from typing import Any, Literal, cast
from time import time
@@ -228,6 +230,16 @@ class VoiceExtension:
logging.debug(f"[VIBE] Radio started feedback: {feedback}")
tracks = await client.rotor_station_tracks(f"{type}:{id}")
self.db.update(gid, {'vibing': True})
if update_settings:
settings = user['vibe_settings']
await client.rotor_station_settings2(
f"{type}:{id}",
mood_energy=settings['mood'],
diversity=settings['diversity'],
language=settings['lang']
)
elif guild['current_track']:
if update_settings:
settings = user['vibe_settings']
@@ -404,7 +416,12 @@ class VoiceExtension:
await channel.send(f"😔 Не удалось загрузить трек. Попробуйте сбросить меню.", delete_after=15)
return None
song = discord.FFmpegPCMAudio(f'music/{gid}.mp3', options='-vn -filter:a "volume=0.15"')
async with aiofiles.open(f'music/{gid}.mp3', "rb") as f:
track_bytes = io.BytesIO(await f.read())
song = discord.FFmpegPCMAudio(track_bytes, pipe=True, options='-vn -filter:a "volume=0.15"')
if not guild['current_menu']:
await asyncio.sleep(1)
vc.play(song, after=lambda exc: asyncio.run_coroutine_threadsafe(self.next_track(ctx, after=True), loop))
logging.info(f"[VC_EXT] Playing track '{track.title}'")
@@ -422,13 +439,25 @@ class VoiceExtension:
return track.title
async def stop_playing(self, ctx: ApplicationContext | Interaction | RawReactionActionEvent, vc: discord.VoiceClient | None = None) -> None:
async def stop_playing(
self, ctx: ApplicationContext | Interaction | RawReactionActionEvent,
*,
vc: discord.VoiceClient | None = None,
full: bool = False
) -> None:
gid = ctx.guild_id if isinstance(ctx, discord.RawReactionActionEvent) else ctx.guild.id if ctx.guild else None
if not gid:
uid = ctx.user_id if isinstance(ctx, discord.RawReactionActionEvent) else ctx.user.id if ctx.user else None
if not gid or not uid:
logging.warning("[VC_EXT] Guild ID not found in context")
return
guild = self.db.get_guild(gid)
if gid in menu_views:
menu_views[gid].stop()
del menu_views[gid]
if not vc:
vc = await self.get_voice_client(ctx)
if vc:
@@ -436,6 +465,49 @@ class VoiceExtension:
self.db.update(gid, {'current_track': None, 'is_stopped': True})
vc.stop()
if full:
if guild['current_menu']:
menu = await self.get_menu_message(ctx, guild['current_menu'])
if menu:
await menu.delete()
self.db.update(gid, {
'current_menu': None, 'repeat': False, 'shuffle': False, 'previous_tracks': [], 'next_tracks': [], 'vibing': False
})
logging.info(f"[VOICE] Playback stopped in guild {gid}")
if guild['vibing']:
user = self.users_db.get_user(uid)
token = user['ym_token']
if not token:
logging.info(f"[VOICE] User {uid} has no YM token")
if not isinstance(ctx, RawReactionActionEvent):
await ctx.respond("❌ Укажите токен через /account login.", delete_after=15, ephemeral=True)
return
client = await self.init_ym_client(ctx, user['ym_token'])
if not client:
logging.info(f"[VOICE] Failed to init YM client for user {uid}")
if not isinstance(ctx, RawReactionActionEvent):
await ctx.respond("❌ Что-то пошло не так. Попробуйте позже.", delete_after=15, ephemeral=True)
return
track = guild['current_track']
if not track:
logging.info(f"[VOICE] No current track in guild {gid}")
if not isinstance(ctx, RawReactionActionEvent):
await ctx.respond("❌ Что-то пошло не так. Попробуйте позже.", delete_after=15, ephemeral=True)
return
res = await client.rotor_station_feedback_track_finished(
f"{user['vibe_type']}:{user['vibe_id']}",
track['id'],
track['duration_ms'] // 1000,
cast(str, user['vibe_batch_id']),
time()
)
logging.info(f"[VOICE] User {uid} finished vibing with result: {res}")
async def next_track(
self,
ctx: ApplicationContext | Interaction | RawReactionActionEvent,
@@ -543,7 +615,7 @@ class VoiceExtension:
next_track,
client=client # type: ignore # Async client can be used here.
)
await self.stop_playing(ctx, vc)
await self.stop_playing(ctx, vc=vc)
title = await self.play_track(
ctx,
ym_track, # type: ignore # de_json should always work here.
@@ -696,6 +768,34 @@ class VoiceExtension:
await client.users_likes_tracks_remove(ym_track.id, client.me.account.uid)
return 'TRACK REMOVED'
async def dislike_track(self, ctx: ApplicationContext | Interaction) -> bool:
"""Dislike current track. Return track title on success.
Args:
ctx (ApplicationContext | Interaction): Context.
Returns:
str | None: Track title or None.
"""
if not ctx.guild or not ctx.user:
logging.warning("[VC_EXT] Guild or User not found in context inside 'dislike_track'")
return False
current_track = self.db.get_track(ctx.guild.id, 'current')
if not current_track:
logging.debug("[VC_EXT] Current track not found in 'dislike_track'")
return False
client = await self.init_ym_client(ctx)
if not client:
return False
res = await client.users_dislikes_tracks_add(
current_track['id'],
client.me.account.uid # type: ignore
)
return res
async def _retry_update_menu_embed(
self,
ctx: ApplicationContext | Interaction,

View File

@@ -1,5 +1,4 @@
import logging
from time import time
from typing import cast
import discord
@@ -241,11 +240,8 @@ class Voice(Cog, VoiceExtension):
await ctx.respond("У вас нет прав для выполнения этой команды.", delete_after=15, ephemeral=True)
return
vc = await self.get_voice_client(ctx)
if await self.voice_check(ctx) and vc:
self.db.update(ctx.guild.id, {'previous_tracks': [], 'next_tracks': [], 'current_track': None, 'is_stopped': True})
vc.stop()
await vc.disconnect(force=True)
if await self.voice_check(ctx):
await self.stop_playing(ctx, full=True)
await ctx.respond("Отключение успешно!", delete_after=15, ephemeral=True)
logging.info(f"[VOICE] Successfully disconnected from voice channel in guild {ctx.guild.id}")
@@ -338,51 +334,7 @@ class Voice(Cog, VoiceExtension):
await ctx.respond("❌ Вы не можете остановить воспроизведение, пока в канале находятся другие пользователи.", delete_after=15, ephemeral=True)
elif await self.voice_check(ctx):
guild = self.db.get_guild(ctx.guild.id)
await self.stop_playing(ctx)
if guild['current_menu']:
menu = await self.get_menu_message(ctx, guild['current_menu'])
if menu:
await menu.delete()
self.db.update(ctx.guild.id, {
'current_menu': None, 'repeat': False, 'shuffle': False, 'previous_tracks': [], 'next_tracks': [], 'vibing': False
})
logging.info(f"[VOICE] Playback stopped in guild {ctx.guild.id}")
if guild['vibing']:
user = self.users_db.get_user(ctx.user.id)
token = user['ym_token']
if not token:
logging.info(f"[VOICE] User {ctx.user.id} has no YM token")
await ctx.respond("❌ Укажите токен через /account login.", delete_after=15, ephemeral=True)
return
client = await self.init_ym_client(ctx, user['ym_token'])
if not client:
logging.info(f"[VOICE] Failed to init YM client for user {ctx.user.id}")
await ctx.respond("❌ Что-то пошло не так. Попробуйте позже.", delete_after=15, ephemeral=True)
return
track = guild['current_track']
if not track:
logging.info(f"[VOICE] No current track in guild {ctx.guild.id}")
await ctx.respond("❌ Что-то пошло не так. Попробуйте позже.", delete_after=15, ephemeral=True)
return
res = await client.rotor_station_feedback_track_finished(
f"{user['vibe_type']}:{user['vibe_id']}",
track['id'],
track['duration_ms'] // 1000,
cast(str, user['vibe_batch_id']),
time()
)
logging.info(f"[VOICE] User {ctx.user.id} finished vibing with result: {res}")
if ctx.guild.id in menu_views:
menu_views[ctx.guild.id].stop()
del menu_views[ctx.guild.id]
await self.stop_playing(ctx, full=True)
await ctx.respond("Воспроизведение остановлено.", delete_after=15, ephemeral=True)
@@ -453,7 +405,7 @@ class Voice(Cog, VoiceExtension):
logging.info(f"[VOICE] Track added to favorites for user {ctx.author.id} in guild {ctx.guild.id}")
await ctx.respond(f"Трек **{result}** был добавлен в избранное.", delete_after=15, ephemeral=True)
@track.command(name='vibe', description="Запустить мою волну по текущему треку.")
@track.command(name='vibe', description="Запустить Мою Волну по текущему треку.")
async def track_vibe(self, ctx: discord.ApplicationContext) -> None:
logging.info(f"[VOICE] Vibe (track) command invoked by user {ctx.author.id} in guild {ctx.guild.id}")
if not await self.voice_check(ctx):