mirror of
https://github.com/deadcxap/YandexMusicDiscordBot.git
synced 2026-01-15 16:21:44 +03:00
feat: Add account playlists view and ability to like current track. Update queue embed.
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
from typing import cast, TypeAlias
|
from math import ceil
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
from discord.ext.commands import Cog
|
from discord.ext.commands import Cog
|
||||||
@@ -12,6 +13,7 @@ from MusicBot.cogs.utils.find import (
|
|||||||
process_album, process_track, process_artist, process_playlist,
|
process_album, process_track, process_artist, process_playlist,
|
||||||
ListenAlbum, ListenTrack, ListenArtist, ListenPlaylist
|
ListenAlbum, ListenTrack, ListenArtist, ListenPlaylist
|
||||||
)
|
)
|
||||||
|
from MusicBot.cogs.utils.misc import MyPlalistsView, generate_playlist_embed
|
||||||
|
|
||||||
def setup(bot):
|
def setup(bot):
|
||||||
bot.add_cog(General(bot))
|
bot.add_cog(General(bot))
|
||||||
@@ -71,7 +73,7 @@ class General(Cog):
|
|||||||
"```/account login <token>```\n"
|
"```/account login <token>```\n"
|
||||||
"Удалить токен из датабазы бота.\n```/account remove```")
|
"Удалить токен из датабазы бота.\n```/account remove```")
|
||||||
elif command == 'queue':
|
elif command == 'queue':
|
||||||
embed.description += ("Получить очередь треков. По 25 элементов на страницу.\n```/queue get```\n"
|
embed.description += ("Получить очередь треков. По 15 элементов на страницу.\n```/queue get```\n"
|
||||||
"Очистить очередь треков и историю прослушивания. Требует согласия части слушателей.\n```/queue clear```\n"
|
"Очистить очередь треков и историю прослушивания. Требует согласия части слушателей.\n```/queue clear```\n"
|
||||||
"`Примечание`: Если вы один в голосовом канале или имеете роль администратора бота, голосование не требуется.")
|
"`Примечание`: Если вы один в голосовом канале или имеете роль администратора бота, голосование не требуется.")
|
||||||
elif command == 'track':
|
elif command == 'track':
|
||||||
@@ -109,6 +111,22 @@ class General(Cog):
|
|||||||
self.db.update(ctx.user.id, {'ym_token': None})
|
self.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)
|
||||||
|
|
||||||
|
@account.command(description="Получить плейлисты пользователя.")
|
||||||
|
async def playlists(self, ctx: discord.ApplicationContext) -> None:
|
||||||
|
token = self.db.get_ym_token(ctx.user.id)
|
||||||
|
if not token:
|
||||||
|
await ctx.respond('❌ Необходимо указать свой токен доступа с помощью команды /login.', delete_after=15, ephemeral=True)
|
||||||
|
return
|
||||||
|
client = await YMClient(token).init()
|
||||||
|
if not client.me or not client.me.account or not client.me.account.uid:
|
||||||
|
await ctx.respond('❌ Что-то пошло не так. Повторите попытку позже.', delete_after=15, ephemeral=True)
|
||||||
|
return
|
||||||
|
playlists_list = await client.users_playlists_list(client.me.account.uid)
|
||||||
|
playlists: list[tuple[str, int]] = [(playlist.title, playlist.track_count) for playlist in playlists_list] # type: ignore
|
||||||
|
self.db.update(ctx.user.id, {'playlists': playlists, 'playlists_page': 0})
|
||||||
|
embed = generate_playlist_embed(0, playlists)
|
||||||
|
await ctx.respond(embed=embed, view=MyPlalistsView(ctx), ephemeral=True)
|
||||||
|
|
||||||
@discord.slash_command(description="Найти контент и отправить информацию о нём. Возвращается лучшее совпадение.")
|
@discord.slash_command(description="Найти контент и отправить информацию о нём. Возвращается лучшее совпадение.")
|
||||||
@discord.option(
|
@discord.option(
|
||||||
"name",
|
"name",
|
||||||
@@ -117,7 +135,7 @@ class General(Cog):
|
|||||||
)
|
)
|
||||||
@discord.option(
|
@discord.option(
|
||||||
"content_type",
|
"content_type",
|
||||||
description="Тип искомого контента (track, album, artist, playlist).",
|
description="Тип искомого контента.",
|
||||||
type=discord.SlashCommandOptionType.string,
|
type=discord.SlashCommandOptionType.string,
|
||||||
choices=['Artist', 'Album', 'Track', 'Playlist'],
|
choices=['Artist', 'Album', 'Track', 'Playlist'],
|
||||||
default='Track'
|
default='Track'
|
||||||
|
|||||||
137
MusicBot/cogs/utils/misc.py
Normal file
137
MusicBot/cogs/utils/misc.py
Normal 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)
|
||||||
@@ -86,32 +86,16 @@ class Player(View, VoiceExtension):
|
|||||||
return
|
return
|
||||||
guild = self.db.get_guild(ctx.guild.id)
|
guild = self.db.get_guild(ctx.guild.id)
|
||||||
|
|
||||||
self.ctx = ctx
|
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.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.play_pause_button = PlayPauseButton(style=ButtonStyle.primary, 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.next_button = NextTrackButton(style=ButtonStyle.primary, emoji='⏭', row=0)
|
||||||
self.prev_button = PrevTrackButton(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)
|
self.queue_button = Button(style=ButtonStyle.primary, emoji='📋', row=1)
|
||||||
|
|
||||||
if guild['repeat']:
|
self.add_item(self.repeat_button)
|
||||||
self.add_item(self.repeat_button_on)
|
|
||||||
else:
|
|
||||||
self.add_item(self.repeat_button_off)
|
|
||||||
|
|
||||||
self.add_item(self.prev_button)
|
self.add_item(self.prev_button)
|
||||||
self.add_item(self.play_pause_button)
|
self.add_item(self.play_pause_button)
|
||||||
self.add_item(self.next_button)
|
self.add_item(self.next_button)
|
||||||
|
self.add_item(self.shuffle_button)
|
||||||
if guild['shuffle']:
|
|
||||||
self.add_item(self.shuffle_button_on)
|
|
||||||
else:
|
|
||||||
self.add_item(self.shuffle_button_off)
|
|
||||||
|
|
||||||
@@ -178,7 +178,7 @@ class VoiceExtension:
|
|||||||
|
|
||||||
token = self.users_db.get_ym_token(ctx.user.id)
|
token = self.users_db.get_ym_token(ctx.user.id)
|
||||||
if not token:
|
if not token:
|
||||||
await ctx.respond("❌ Необходимо указать свой токен доступа с помощью комманды /login.", delete_after=15, ephemeral=True)
|
await ctx.respond("❌ Необходимо указать свой токен доступа с помощью команды /login.", delete_after=15, ephemeral=True)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
channel = ctx.channel
|
channel = ctx.channel
|
||||||
@@ -239,10 +239,10 @@ class VoiceExtension:
|
|||||||
|
|
||||||
gid = ctx.guild.id
|
gid = ctx.guild.id
|
||||||
guild = self.db.get_guild(gid)
|
guild = self.db.get_guild(gid)
|
||||||
await track.download_async(f'music/{ctx.guild_id}.mp3')
|
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"')
|
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.set_current_track(gid, track)
|
||||||
self.db.update(gid, {'is_stopped': False})
|
self.db.update(gid, {'is_stopped': False})
|
||||||
@@ -253,18 +253,6 @@ class VoiceExtension:
|
|||||||
|
|
||||||
return track.title
|
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:
|
def stop_playing(self, ctx: ApplicationContext | Interaction) -> None:
|
||||||
if not ctx.guild:
|
if not ctx.guild:
|
||||||
return
|
return
|
||||||
@@ -275,12 +263,13 @@ class VoiceExtension:
|
|||||||
vc.stop()
|
vc.stop()
|
||||||
return
|
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.
|
"""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.
|
Doesn't change track if stopped. Stop playing if tracks list is empty.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
ctx (ApplicationContext | Interaction): Context
|
ctx (ApplicationContext | Interaction): Context
|
||||||
|
after (bool, optional): Whether the function was called by the after callback. Defaults to False.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str | None: Track title or None.
|
str | None: Track title or None.
|
||||||
@@ -300,7 +289,7 @@ class VoiceExtension:
|
|||||||
current_track = guild['current_track']
|
current_track = guild['current_track']
|
||||||
ym_track = None
|
ym_track = None
|
||||||
|
|
||||||
if guild['repeat'] and current_track:
|
if guild['repeat'] and after:
|
||||||
return await self.repeat_current_track(ctx)
|
return await self.repeat_current_track(ctx)
|
||||||
elif guild['shuffle']:
|
elif guild['shuffle']:
|
||||||
next_track = self.db.get_random_track(gid)
|
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 await self.play_track(ctx, ym_track) # type: ignore
|
||||||
|
|
||||||
return None
|
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
|
||||||
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from math import ceil
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
@@ -7,6 +8,7 @@ from yandex_music import Track, ClientAsync
|
|||||||
|
|
||||||
from MusicBot.cogs.utils.voice import VoiceExtension, generate_player_embed
|
from MusicBot.cogs.utils.voice import VoiceExtension, generate_player_embed
|
||||||
from MusicBot.cogs.utils.player import Player
|
from MusicBot.cogs.utils.player import Player
|
||||||
|
from MusicBot.cogs.utils.misc import QueueView, generate_queue_embed
|
||||||
|
|
||||||
def setup(bot: discord.Bot):
|
def setup(bot: discord.Bot):
|
||||||
bot.add_cog(Voice())
|
bot.add_cog(Voice())
|
||||||
@@ -74,23 +76,17 @@ class Voice(Cog, VoiceExtension):
|
|||||||
async def get(self, ctx: discord.ApplicationContext) -> None:
|
async def get(self, ctx: discord.ApplicationContext) -> None:
|
||||||
if not await self.voice_check(ctx):
|
if not await self.voice_check(ctx):
|
||||||
return
|
return
|
||||||
tracks_list = self.db.get_tracks_list(ctx.guild.id, 'next')
|
tracks = self.db.get_tracks_list(ctx.guild.id, 'next')
|
||||||
embed = discord.Embed(
|
self.users_db.update(ctx.user.id, {'queue_page': 0})
|
||||||
title='Список треков',
|
embed = generate_queue_embed(0, tracks)
|
||||||
color=discord.Color.dark_purple()
|
await ctx.respond(embed=embed, view=QueueView(ctx), ephemeral=True)
|
||||||
)
|
|
||||||
for i, track in enumerate(tracks_list, start=1):
|
|
||||||
embed.add_field(name=f"{i} - {track.get('title')}", value="", inline=False)
|
|
||||||
if i == 25:
|
|
||||||
break
|
|
||||||
await ctx.respond("", embed=embed, ephemeral=True)
|
|
||||||
|
|
||||||
@track.command(description="Приостановить текущий трек.")
|
@track.command(description="Приостановить текущий трек.")
|
||||||
async def pause(self, ctx: discord.ApplicationContext) -> None:
|
async def pause(self, ctx: discord.ApplicationContext) -> None:
|
||||||
vc = self.get_voice_client(ctx)
|
vc = self.get_voice_client(ctx)
|
||||||
if await self.voice_check(ctx) and vc is not None:
|
if await self.voice_check(ctx) and vc is not None:
|
||||||
if not vc.is_paused():
|
if not vc.is_paused():
|
||||||
self.pause_playing(ctx)
|
vc.pause()
|
||||||
await ctx.respond("Воспроизведение приостановлено.", delete_after=15, ephemeral=True)
|
await ctx.respond("Воспроизведение приостановлено.", delete_after=15, ephemeral=True)
|
||||||
else:
|
else:
|
||||||
await ctx.respond("Воспроизведение уже приостановлено.", delete_after=15, ephemeral=True)
|
await ctx.respond("Воспроизведение уже приостановлено.", delete_after=15, ephemeral=True)
|
||||||
@@ -100,7 +96,7 @@ class Voice(Cog, VoiceExtension):
|
|||||||
vc = self.get_voice_client(ctx)
|
vc = self.get_voice_client(ctx)
|
||||||
if await self.voice_check(ctx) and vc is not None:
|
if await self.voice_check(ctx) and vc is not None:
|
||||||
if vc.is_paused():
|
if vc.is_paused():
|
||||||
self.resume_playing(ctx)
|
vc.resume()
|
||||||
await ctx.respond("Воспроизведение восстановлено.", delete_after=15, ephemeral=True)
|
await ctx.respond("Воспроизведение восстановлено.", delete_after=15, ephemeral=True)
|
||||||
else:
|
else:
|
||||||
await ctx.respond("Воспроизведение не на паузе.", delete_after=15, ephemeral=True)
|
await ctx.respond("Воспроизведение не на паузе.", delete_after=15, ephemeral=True)
|
||||||
@@ -131,3 +127,15 @@ class Voice(Cog, VoiceExtension):
|
|||||||
await ctx.respond(f"Сейчас играет: **{title}**!", delete_after=15)
|
await ctx.respond(f"Сейчас играет: **{title}**!", delete_after=15)
|
||||||
else:
|
else:
|
||||||
await ctx.respond(f"Нет треков в очереди.", delete_after=15, ephemeral=True)
|
await ctx.respond(f"Нет треков в очереди.", delete_after=15, ephemeral=True)
|
||||||
|
|
||||||
|
@voice.command(description="Добавить трек в избранное.")
|
||||||
|
async def like(self, ctx: discord.ApplicationContext) -> None:
|
||||||
|
if await self.voice_check(ctx):
|
||||||
|
vc = self.get_voice_client(ctx)
|
||||||
|
if not vc or not vc.is_playing:
|
||||||
|
await ctx.respond("Нет воспроизводимого трека.", delete_after=15, ephemeral=True)
|
||||||
|
result = await self.like_track(ctx)
|
||||||
|
if not result:
|
||||||
|
await ctx.respond("Трек уже добавлен в избранное.", delete_after=15, ephemeral=True)
|
||||||
|
else:
|
||||||
|
await ctx.respond(f"Трек **{result}** был добавлен в избранное.", delete_after=15, ephemeral=True)
|
||||||
|
|||||||
@@ -23,7 +23,10 @@ class BaseUsersDatabase:
|
|||||||
uid = uid
|
uid = uid
|
||||||
users.insert_one(ExplicitUser(
|
users.insert_one(ExplicitUser(
|
||||||
_id=uid,
|
_id=uid,
|
||||||
ym_token=None
|
ym_token=None,
|
||||||
|
playlists=[],
|
||||||
|
playlists_page=0,
|
||||||
|
queue_page=0
|
||||||
))
|
))
|
||||||
|
|
||||||
def update(self, uid: int, data: User) -> None:
|
def update(self, uid: int, data: User) -> None:
|
||||||
|
|||||||
@@ -2,7 +2,13 @@ from typing import TypedDict
|
|||||||
|
|
||||||
class User(TypedDict, total=False):
|
class User(TypedDict, total=False):
|
||||||
ym_token: str | None
|
ym_token: str | None
|
||||||
|
playlists: list[tuple[str, int]]
|
||||||
|
playlists_page: int
|
||||||
|
queue_page: int
|
||||||
|
|
||||||
class ExplicitUser(TypedDict):
|
class ExplicitUser(TypedDict):
|
||||||
_id: int
|
_id: int
|
||||||
ym_token: str | None
|
ym_token: str | None
|
||||||
|
playlists: list[tuple[str, int]] # name / tracks count
|
||||||
|
playlists_page: int
|
||||||
|
queue_page: int
|
||||||
|
|||||||
Reference in New Issue
Block a user