feat: Add account playlists view and ability to like current track. Update queue embed.

This commit is contained in:
Lemon4ksan
2025-01-14 17:05:12 +03:00
parent eb6c9b4665
commit c2feeec158
7 changed files with 229 additions and 54 deletions

137
MusicBot/cogs/utils/misc.py Normal file
View File

@@ -0,0 +1,137 @@
from math import ceil
from typing import Any
from discord.ui import View, Button, Item
from discord import ButtonStyle, Interaction, ApplicationContext, Embed
from MusicBot.cogs.utils.voice import VoiceExtension
def generate_playlist_embed(page: int, playlists: list[tuple[str, int]]) -> Embed:
count = 15 * page
length = len(playlists)
embed = Embed(
title=f"Всего плейлистов: {length}",
color=0xfed42b
)
embed.set_author(name="Ваши плейлисты")
embed.set_footer(text=f"Страница {page + 1} из {ceil(length / 10)}")
for playlist in playlists[count:count + 10]:
embed.add_field(name=playlist[0], value=f"{playlist[1]} треков", inline=False)
return embed
def generate_queue_embed(page: int, tracks_list: list[dict[str, Any]]) -> Embed:
count = 15 * page
length = len(tracks_list)
embed = Embed(
title=f"Всего: {length}",
color=0xfed42b,
)
embed.set_author(name="Очередь треков")
embed.set_footer(text=f"Страница {page + 1} из {ceil(length / 15)}")
for i, track in enumerate(tracks_list[count:count + 15], start=1 + count):
duration = track['duration_ms']
duration_m = duration // 60000
duration_s = ceil(duration / 1000) - duration_m * 60
embed.add_field(name=f"{i} - {track['title']} - {duration_m}:{duration_s:02d}", value="", inline=False)
return embed
class MPNextButton(Button, VoiceExtension):
def __init__(self, **kwargs):
Button.__init__(self, **kwargs)
VoiceExtension.__init__(self)
async def callback(self, interaction: Interaction) -> None:
if not interaction.user:
return
user = self.users_db.get_user(interaction.user.id)
page = user['playlists_page'] + 1
self.users_db.update(interaction.user.id, {'playlists_page': page})
embed = generate_playlist_embed(page, user['playlists'])
await interaction.edit(embed=embed, view=MyPlalistsView(interaction))
class MPPrevButton(Button, VoiceExtension):
def __init__(self, **kwargs):
Button.__init__(self, **kwargs)
VoiceExtension.__init__(self)
async def callback(self, interaction: Interaction) -> None:
if not interaction.user:
return
user = self.users_db.get_user(interaction.user.id)
page = user['playlists_page'] - 1
self.users_db.update(interaction.user.id, {'playlists_page': page})
embed = generate_playlist_embed(page, user['playlists'])
await interaction.edit(embed=embed, view=MyPlalistsView(interaction))
class MyPlalistsView(View, VoiceExtension):
def __init__(self, ctx: ApplicationContext | Interaction, *items: Item, timeout: float | None = 3600, disable_on_timeout: bool = True):
View.__init__(self, *items, timeout=timeout, disable_on_timeout=disable_on_timeout)
VoiceExtension.__init__(self)
if not ctx.user:
return
user = self.users_db.get_user(ctx.user.id)
count = 10 * user['playlists_page']
next_button = MPNextButton(style=ButtonStyle.primary, emoji='▶️')
prev_button = MPPrevButton(style=ButtonStyle.primary, emoji='◀️')
if not user['playlists'][count + 10:]:
next_button.disabled = True
if not user['playlists'][:count]:
prev_button.disabled = True
self.add_item(prev_button)
self.add_item(next_button)
class QNextButton(Button, VoiceExtension):
def __init__(self, **kwargs):
Button.__init__(self, **kwargs)
VoiceExtension.__init__(self)
async def callback(self, interaction: Interaction) -> None:
if not interaction.user or not interaction.guild:
return
user = self.users_db.get_user(interaction.user.id)
page = user['queue_page'] + 1
self.users_db.update(interaction.user.id, {'queue_page': page})
tracks = self.db.get_tracks_list(interaction.guild.id, 'next')
embed = generate_queue_embed(page, tracks)
await interaction.edit(embed=embed, view=QueueView(interaction))
class QPrevButton(Button, VoiceExtension):
def __init__(self, **kwargs):
Button.__init__(self, **kwargs)
VoiceExtension.__init__(self)
async def callback(self, interaction: Interaction) -> None:
if not interaction.user or not interaction.guild:
return
user = self.users_db.get_user(interaction.user.id)
page = user['queue_page'] - 1
self.users_db.update(interaction.user.id, {'queue_page': page})
tracks = self.db.get_tracks_list(interaction.guild.id, 'next')
embed = generate_queue_embed(page, tracks)
await interaction.edit(embed=embed, view=QueueView(interaction))
class QueueView(View, VoiceExtension):
def __init__(self, ctx: ApplicationContext | Interaction, *items: Item, timeout: float | None = 3600, disable_on_timeout: bool = True):
View.__init__(self, *items, timeout=timeout, disable_on_timeout=disable_on_timeout)
VoiceExtension.__init__(self)
if not ctx.user or not ctx.guild:
return
tracks = self.db.get_tracks_list(ctx.guild.id, 'next')
user = self.users_db.get_user(ctx.user.id)
count = 15 * user['queue_page']
next_button = QNextButton(style=ButtonStyle.primary, emoji='▶️')
prev_button = QPrevButton(style=ButtonStyle.primary, emoji='◀️')
if not tracks[count + 15:]:
next_button.disabled = True
if not tracks[:count]:
prev_button.disabled = True
self.add_item(prev_button)
self.add_item(next_button)

View File

@@ -86,32 +86,16 @@ class Player(View, VoiceExtension):
return
guild = self.db.get_guild(ctx.guild.id)
self.ctx = ctx
self.repeat_button_off = ToggleRepeatButton(style=ButtonStyle.secondary, emoji='🔂', row=0)
self.repeat_button_on = ToggleRepeatButton(style=ButtonStyle.success, emoji='🔂', row=0)
self.shuffle_button_off = ToggleShuffleButton(style=ButtonStyle.secondary, emoji='🔀', row=0)
self.shuffle_button_on = ToggleShuffleButton(style=ButtonStyle.success, emoji='🔀', row=0)
self.repeat_button = ToggleRepeatButton(style=ButtonStyle.success if guild['repeat'] else ButtonStyle.secondary, emoji='🔂', row=0)
self.shuffle_button = ToggleShuffleButton(style=ButtonStyle.success if guild['shuffle'] else ButtonStyle.secondary, emoji='🔀', row=0)
self.play_pause_button = PlayPauseButton(style=ButtonStyle.primary, emoji='', row=0)
self.next_button = NextTrackButton(style=ButtonStyle.primary, emoji='', row=0)
self.prev_button = PrevTrackButton(style=ButtonStyle.primary, emoji='', row=0)
self.queue_button = Button(style=ButtonStyle.primary, emoji='📋', row=1)
if guild['repeat']:
self.add_item(self.repeat_button_on)
else:
self.add_item(self.repeat_button_off)
self.add_item(self.repeat_button)
self.add_item(self.prev_button)
self.add_item(self.play_pause_button)
self.add_item(self.next_button)
if guild['shuffle']:
self.add_item(self.shuffle_button_on)
else:
self.add_item(self.shuffle_button_off)
self.add_item(self.shuffle_button)

View File

@@ -178,7 +178,7 @@ class VoiceExtension:
token = self.users_db.get_ym_token(ctx.user.id)
if not token:
await ctx.respond("❌ Необходимо указать свой токен доступа с помощью комманды /login.", delete_after=15, ephemeral=True)
await ctx.respond("❌ Необходимо указать свой токен доступа с помощью команды /login.", delete_after=15, ephemeral=True)
return False
channel = ctx.channel
@@ -239,10 +239,10 @@ class VoiceExtension:
gid = ctx.guild.id
guild = self.db.get_guild(gid)
await track.download_async(f'music/{ctx.guild_id}.mp3')
song = discord.FFmpegPCMAudio(f'music/{ctx.guild_id}.mp3', options='-vn -filter:a "volume=0.15"')
await track.download_async(f'music/{ctx.guild.id}.mp3')
song = discord.FFmpegPCMAudio(f'music/{ctx.guild.id}.mp3', options='-vn -filter:a "volume=0.15"')
vc.play(song, after=lambda exc: asyncio.run_coroutine_threadsafe(self.next_track(ctx), loop))
vc.play(song, after=lambda exc: asyncio.run_coroutine_threadsafe(self.next_track(ctx, after=True), loop))
self.db.set_current_track(gid, track)
self.db.update(gid, {'is_stopped': False})
@@ -253,18 +253,6 @@ class VoiceExtension:
return track.title
def pause_playing(self, ctx: ApplicationContext | Interaction) -> None:
vc = self.get_voice_client(ctx)
if vc:
vc.pause()
return
def resume_playing(self, ctx: ApplicationContext | Interaction) -> None:
vc = self.get_voice_client(ctx)
if vc:
vc.resume()
return
def stop_playing(self, ctx: ApplicationContext | Interaction) -> None:
if not ctx.guild:
return
@@ -275,12 +263,13 @@ class VoiceExtension:
vc.stop()
return
async def next_track(self, ctx: ApplicationContext | Interaction) -> str | None:
async def next_track(self, ctx: ApplicationContext | Interaction, *, after: bool = False) -> str | None:
"""Switch to the next track in the queue. Return track title on success.
Doesn't change track if stopped. Stop playing if tracks list is empty.
Args:
ctx (ApplicationContext | Interaction): Context
after (bool, optional): Whether the function was called by the after callback. Defaults to False.
Returns:
str | None: Track title or None.
@@ -300,7 +289,7 @@ class VoiceExtension:
current_track = guild['current_track']
ym_track = None
if guild['repeat'] and current_track:
if guild['repeat'] and after:
return await self.repeat_current_track(ctx)
elif guild['shuffle']:
next_track = self.db.get_random_track(gid)
@@ -369,3 +358,33 @@ class VoiceExtension:
return await self.play_track(ctx, ym_track) # type: ignore
return None
async def like_track(self, ctx: ApplicationContext | Interaction) -> str | None:
"""Like 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:
return None
current_track = self.db.get_track(ctx.guild.id, 'current')
token = self.users_db.get_ym_token(ctx.user.id)
if not current_track or not token:
return None
client = await ClientAsync(token).init()
likes = await client.users_likes_tracks()
if not likes:
return None
ym_track = cast(Track, Track.de_json(current_track, client=client)) # type: ignore
if ym_track.id not in [track.id for track in likes.tracks]:
await ym_track.like_async()
return ym_track.title
return None