mirror of
https://github.com/RafaelSolVargas/Vulkan.git
synced 2025-10-29 16:57:23 +00:00
Adding pages to songs queue to move between all queue, adding buttons to queue embed to better user experience
This commit is contained in:
parent
6ba7734a36
commit
2627f95a6d
@ -19,12 +19,14 @@ class VConfigs(Singleton):
|
||||
|
||||
self.CLEANER_MESSAGES_QUANT = 5
|
||||
self.ACQUIRE_LOCK_TIMEOUT = 10
|
||||
self.QUEUE_VIEW_TIMEOUT = 120
|
||||
self.COMMANDS_FOLDER_NAME = 'DiscordCogs'
|
||||
self.COMMANDS_PATH = f'{Folder().rootFolder}{self.COMMANDS_FOLDER_NAME}'
|
||||
self.VC_TIMEOUT = 300
|
||||
|
||||
self.MAX_PLAYLIST_LENGTH = 50
|
||||
self.MAX_PLAYLIST_FORCED_LENGTH = 5
|
||||
self.MAX_SONGS_IN_PAGE = 10
|
||||
self.MAX_PRELOAD_SONGS = 15
|
||||
self.MAX_SONGS_HISTORY = 15
|
||||
|
||||
|
||||
@ -34,6 +34,14 @@ class VEmbeds:
|
||||
)
|
||||
return embed
|
||||
|
||||
def INVALID_INDEX(self) -> Embed:
|
||||
embed = Embed(
|
||||
title=self.__messages.BAD_COMMAND_TITLE,
|
||||
description=self.__messages.INVALID_INDEX,
|
||||
colour=self.__colors.BLACK
|
||||
)
|
||||
return embed
|
||||
|
||||
def SONG_ADDED_TWO(self, info: dict, pos: int) -> Embed:
|
||||
embed = self.SONG_INFO(info, self.__messages.SONG_ADDED_TWO, pos)
|
||||
return embed
|
||||
@ -162,6 +170,14 @@ class VEmbeds:
|
||||
)
|
||||
return embed
|
||||
|
||||
def INVALID_ARGUMENTS(self):
|
||||
embed = Embed(
|
||||
title=self.__messages.BAD_COMMAND_TITLE,
|
||||
description=self.__messages.INVALID_ARGUMENTS,
|
||||
colour=self.__colors.BLACK
|
||||
)
|
||||
return embed
|
||||
|
||||
def COMMAND_NOT_FOUND(self) -> Embed:
|
||||
embed = Embed(
|
||||
title=self.__messages.COMMAND_NOT_FOUND_TITLE,
|
||||
|
||||
@ -79,6 +79,11 @@ class ErrorRemoving(VulkanError):
|
||||
super().__init__(message, title, *args)
|
||||
|
||||
|
||||
class InvalidIndex(VulkanError):
|
||||
def __init__(self, message='', title='', *args: object) -> None:
|
||||
super().__init__(message, title, *args)
|
||||
|
||||
|
||||
class NumberRequired(VulkanError):
|
||||
def __init__(self, message='', title='', *args: object) -> None:
|
||||
super().__init__(message, title, *args)
|
||||
|
||||
@ -64,6 +64,8 @@ class Messages(Singleton):
|
||||
self.NO_CHANNEL = 'To play some music, connect to any voice channel first.'
|
||||
self.NO_GUILD = f'This server does not has a Player, try {configs.BOT_PREFIX}reset'
|
||||
self.INVALID_INPUT = f'This URL was too strange, try something better or type {configs.BOT_PREFIX}help play'
|
||||
self.INVALID_INDEX = f'Invalid index passed as argument.'
|
||||
self.INVALID_ARGUMENTS = f'Invalid arguments passed to command.'
|
||||
self.DOWNLOADING_ERROR = f"{self.__emojis.ERROR} It's impossible to download and play this video"
|
||||
self.EXTRACTING_ERROR = f'{self.__emojis.ERROR} An error ocurred while searching for the songs'
|
||||
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
from discord.ext.commands import Context, command, Cog
|
||||
from Config.Exceptions import InvalidInput
|
||||
from Config.Helper import Helper
|
||||
from Handlers.ClearHandler import ClearHandler
|
||||
from Handlers.HandlerResponse import HandlerResponse
|
||||
from Handlers.MoveHandler import MoveHandler
|
||||
from Handlers.NowPlayingHandler import NowPlayingHandler
|
||||
from Handlers.PlayHandler import PlayHandler
|
||||
@ -19,6 +21,7 @@ from UI.Responses.EmoteCogResponse import EmoteCommandResponse
|
||||
from UI.Responses.EmbedCogResponse import EmbedCommandResponse
|
||||
from Music.VulkanBot import VulkanBot
|
||||
from Config.Configs import VConfigs
|
||||
from Config.Embeds import VEmbeds
|
||||
from Parallelism.ProcessManager import ProcessManager
|
||||
|
||||
helper = Helper()
|
||||
@ -33,6 +36,7 @@ class MusicCog(Cog):
|
||||
|
||||
def __init__(self, bot: VulkanBot) -> None:
|
||||
self.__bot: VulkanBot = bot
|
||||
self.__embeds = VEmbeds()
|
||||
VConfigs().setProcessManager(ProcessManager(bot))
|
||||
|
||||
@command(name="play", help=helper.HELP_PLAY, description=helper.HELP_PLAY_LONG, aliases=['p', 'tocar'])
|
||||
@ -50,13 +54,27 @@ class MusicCog(Cog):
|
||||
print(f'[ERROR IN COG] -> {e}')
|
||||
|
||||
@command(name="queue", help=helper.HELP_QUEUE, description=helper.HELP_QUEUE_LONG, aliases=['q', 'fila', 'musicas'])
|
||||
async def queue(self, ctx: Context) -> None:
|
||||
async def queue(self, ctx: Context, *args) -> None:
|
||||
try:
|
||||
controller = QueueHandler(ctx, self.__bot)
|
||||
pageNumber = " ".join(args)
|
||||
|
||||
response = await controller.run()
|
||||
view2 = EmbedCommandResponse(response)
|
||||
await view2.run()
|
||||
controller = QueueHandler(ctx, self.__bot,)
|
||||
|
||||
if pageNumber == "":
|
||||
response = await controller.run()
|
||||
else:
|
||||
pageNumber = int(pageNumber)
|
||||
pageNumber -= 1 # Change index 1 to 0
|
||||
response = await controller.run(pageNumber)
|
||||
|
||||
view = EmbedCommandResponse(response)
|
||||
await view.run()
|
||||
except ValueError as e:
|
||||
error = InvalidInput()
|
||||
embed = self.__embeds.INVALID_ARGUMENTS()
|
||||
response = HandlerResponse(ctx, embed, error)
|
||||
view = EmbedCommandResponse(response)
|
||||
await view.run()
|
||||
except Exception as e:
|
||||
print(f'[ERROR IN COG] -> {e}')
|
||||
|
||||
|
||||
@ -2,14 +2,16 @@ from typing import Union
|
||||
from discord.ext.commands import Context
|
||||
from Config.Exceptions import VulkanError
|
||||
from discord import Embed, Interaction
|
||||
from discord.ui import View
|
||||
|
||||
|
||||
class HandlerResponse:
|
||||
def __init__(self, ctx: Union[Context, Interaction], embed: Embed = None, error: VulkanError = None) -> None:
|
||||
def __init__(self, ctx: Union[Context, Interaction], embed: Embed = None, error: VulkanError = None, view=None) -> None:
|
||||
self.__ctx: Context = ctx
|
||||
self.__error: VulkanError = error
|
||||
self.__embed: Embed = embed
|
||||
self.__success = False if error else True
|
||||
self.__view = view
|
||||
|
||||
@property
|
||||
def ctx(self) -> Union[Context, Interaction]:
|
||||
@ -19,6 +21,10 @@ class HandlerResponse:
|
||||
def embed(self) -> Union[Embed, None]:
|
||||
return self.__embed
|
||||
|
||||
@property
|
||||
def view(self) -> View:
|
||||
return self.__view
|
||||
|
||||
def error(self) -> Union[VulkanError, None]:
|
||||
return self.__error
|
||||
|
||||
|
||||
@ -1,19 +1,23 @@
|
||||
from discord.ext.commands import Context
|
||||
from Config.Exceptions import InvalidIndex
|
||||
from Handlers.AbstractHandler import AbstractHandler
|
||||
from Handlers.HandlerResponse import HandlerResponse
|
||||
from Music.Downloader import Downloader
|
||||
from UI.Views.EmptyView import EmptyView
|
||||
from Utils.Utils import Utils
|
||||
from Music.VulkanBot import VulkanBot
|
||||
from typing import Union
|
||||
from discord import Interaction
|
||||
from Music.Song import Song
|
||||
from Music.Playlist import Playlist
|
||||
from typing import List, Union
|
||||
from discord import Button, Interaction
|
||||
from UI.Buttons.EmptyButton import EmptyButton
|
||||
from Config.Emojis import VEmojis
|
||||
|
||||
|
||||
class QueueHandler(AbstractHandler):
|
||||
def __init__(self, ctx: Union[Context, Interaction], bot: VulkanBot) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
self.__down = Downloader()
|
||||
|
||||
async def run(self) -> HandlerResponse:
|
||||
async def run(self, pageNumber=0) -> HandlerResponse:
|
||||
# Retrieve the process of the guild
|
||||
processManager = self.config.getProcessManager()
|
||||
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
||||
@ -25,7 +29,7 @@ class QueueHandler(AbstractHandler):
|
||||
processLock = processInfo.getLock()
|
||||
acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
|
||||
if acquired:
|
||||
playlist = processInfo.getPlaylist()
|
||||
playlist: Playlist = processInfo.getPlaylist()
|
||||
|
||||
if playlist.isLoopingOne():
|
||||
song = playlist.getCurrentSong()
|
||||
@ -33,13 +37,25 @@ class QueueHandler(AbstractHandler):
|
||||
processLock.release() # Release the Lock
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
|
||||
songs_preload = playlist.getSongsToPreload()
|
||||
songsPages = playlist.getSongsPages()
|
||||
if pageNumber < 0 or pageNumber >= len(songsPages):
|
||||
embed = self.embeds.INVALID_INDEX()
|
||||
error = InvalidIndex()
|
||||
processLock.release() # Release the Lock
|
||||
return HandlerResponse(self.ctx, embed, error)
|
||||
|
||||
allSongs = playlist.getSongs()
|
||||
if len(songs_preload) == 0:
|
||||
if len(allSongs) == 0:
|
||||
embed = self.embeds.EMPTY_QUEUE()
|
||||
processLock.release() # Release the Lock
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
|
||||
# Select the page in queue to be printed
|
||||
songs = songsPages[pageNumber]
|
||||
# Create view for this embed
|
||||
buttons = self.__createViewButtons(songsPages, pageNumber)
|
||||
queueView = EmptyView(self.bot, buttons, self.config.QUEUE_VIEW_TIMEOUT)
|
||||
|
||||
if playlist.isLoopingAll():
|
||||
title = self.messages.ALL_SONGS_LOOPING
|
||||
else:
|
||||
@ -49,17 +65,33 @@ class QueueHandler(AbstractHandler):
|
||||
for song in allSongs]))
|
||||
total_songs = len(playlist.getSongs())
|
||||
|
||||
text = f'📜 Queue length: {total_songs} | ⌛ Duration: `{total_time}` downloaded \n\n'
|
||||
text = f'📜 Queue length: {total_songs} | Page Number: {pageNumber+1}/{len(songsPages)} | ⌛ Duration: `{total_time}` downloaded \n\n'
|
||||
|
||||
for pos, song in enumerate(songs_preload, start=1):
|
||||
# To work get the correct index of all songs
|
||||
startIndex = (pageNumber * self.config.MAX_SONGS_IN_PAGE) + 1
|
||||
for pos, song in enumerate(songs, start=startIndex):
|
||||
song_name = song.title if song.title else self.messages.SONG_DOWNLOADING
|
||||
text += f"**`{pos}` - ** {song_name} - `{Utils.format_time(song.duration)}`\n"
|
||||
|
||||
embed = self.embeds.QUEUE(title, text)
|
||||
# Release the acquired Lock
|
||||
processLock.release()
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
return HandlerResponse(self.ctx, embed, view=queueView)
|
||||
else:
|
||||
processManager.resetProcess(self.guild, self.ctx)
|
||||
embed = self.embeds.PLAYER_RESTARTED()
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
|
||||
def __createViewButtons(self, songsPages: List[List[Song]], pageNumber: int) -> List[Button]:
|
||||
buttons = []
|
||||
if pageNumber > 0:
|
||||
prevPageNumber = pageNumber - 1
|
||||
buttons.append(EmptyButton(self.bot, self.run, VEmojis().BACK,
|
||||
"Prev Page", pageNumber=prevPageNumber))
|
||||
|
||||
if pageNumber < len(songsPages) - 1:
|
||||
nextPageNumber = pageNumber + 1
|
||||
buttons.append(EmptyButton(self.bot, self.run, VEmojis().SKIP,
|
||||
"Next Page", pageNumber=nextPageNumber))
|
||||
|
||||
return buttons
|
||||
|
||||
@ -50,6 +50,15 @@ class Playlist:
|
||||
def getSongsToPreload(self) -> List[Song]:
|
||||
return list(self.__queue)[:self.__configs.MAX_PRELOAD_SONGS]
|
||||
|
||||
def getSongsPages(self) -> List[List[Song]]:
|
||||
songsPages = []
|
||||
for x in range(0, len(self.__queue), self.__configs.MAX_SONGS_IN_PAGE):
|
||||
endIndex = x + self.__configs.MAX_SONGS_IN_PAGE
|
||||
startIndex = x
|
||||
songsPages.append(list(self.__queue)[startIndex:endIndex])
|
||||
|
||||
return songsPages
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.__queue)
|
||||
|
||||
|
||||
@ -42,6 +42,7 @@ class VulkanInitializer:
|
||||
cogsStatus.append(bot.load_extension(cogPath, store=True))
|
||||
|
||||
if len(bot.cogs.keys()) != self.__getTotalCogs():
|
||||
print(cogsStatus)
|
||||
raise VulkanError(message='Failed to load some Cog')
|
||||
|
||||
except VulkanError as e:
|
||||
|
||||
@ -18,5 +18,7 @@ class BackButton(Button):
|
||||
handler = PrevHandler(interaction, self.__bot)
|
||||
response = await handler.run()
|
||||
|
||||
if response.embed:
|
||||
if response and response.view is not None:
|
||||
await interaction.followup.send(embed=response.embed, view=response.view)
|
||||
elif response:
|
||||
await interaction.followup.send(embed=response.embed)
|
||||
|
||||
26
UI/Buttons/EmptyButton.py
Normal file
26
UI/Buttons/EmptyButton.py
Normal file
@ -0,0 +1,26 @@
|
||||
from typing import Awaitable
|
||||
from discord import ButtonStyle, Interaction
|
||||
from discord.ui import Button
|
||||
from Handlers.HandlerResponse import HandlerResponse
|
||||
from Music.VulkanBot import VulkanBot
|
||||
|
||||
|
||||
class EmptyButton(Button):
|
||||
def __init__(self, bot: VulkanBot, cb: Awaitable, emoji, label=None, *args, **kwargs):
|
||||
super().__init__(label=label, style=ButtonStyle.secondary, emoji=emoji)
|
||||
self.__bot = bot
|
||||
self.__args = args
|
||||
self.__kwargs = kwargs
|
||||
self.__callback = cb
|
||||
|
||||
async def callback(self, interaction: Interaction) -> None:
|
||||
"""Callback to when Button is clicked"""
|
||||
# Return to Discord that this command is being processed
|
||||
await interaction.response.defer()
|
||||
|
||||
response: HandlerResponse = await self.__callback(*self.__args, **self.__kwargs)
|
||||
|
||||
if response and response.view is not None:
|
||||
await interaction.followup.send(embed=response.embed, view=response.view)
|
||||
elif response:
|
||||
await interaction.followup.send(embed=response.embed)
|
||||
@ -16,5 +16,7 @@ class LoopAllButton(Button):
|
||||
handler = LoopHandler(interaction, self.__bot)
|
||||
response = await handler.run('all')
|
||||
|
||||
if response.embed:
|
||||
if response and response.view is not None:
|
||||
await interaction.followup.send(embed=response.embed, view=response.view)
|
||||
elif response:
|
||||
await interaction.followup.send(embed=response.embed)
|
||||
|
||||
@ -16,5 +16,7 @@ class LoopOffButton(Button):
|
||||
handler = LoopHandler(interaction, self.__bot)
|
||||
response = await handler.run('off')
|
||||
|
||||
if response.embed:
|
||||
if response and response.view is not None:
|
||||
await interaction.followup.send(embed=response.embed, view=response.view)
|
||||
elif response:
|
||||
await interaction.followup.send(embed=response.embed)
|
||||
|
||||
@ -16,5 +16,7 @@ class LoopOneButton(Button):
|
||||
handler = LoopHandler(interaction, self.__bot)
|
||||
response = await handler.run('one')
|
||||
|
||||
if response.embed:
|
||||
if response and response.view is not None:
|
||||
await interaction.followup.send(embed=response.embed, view=response.view)
|
||||
elif response:
|
||||
await interaction.followup.send(embed=response.embed)
|
||||
|
||||
@ -16,5 +16,7 @@ class PauseButton(Button):
|
||||
handler = PauseHandler(interaction, self.__bot)
|
||||
response = await handler.run()
|
||||
|
||||
if response.embed:
|
||||
if response and response.view is not None:
|
||||
await interaction.followup.send(embed=response.embed, view=response.view)
|
||||
elif response:
|
||||
await interaction.followup.send(embed=response.embed)
|
||||
|
||||
@ -16,5 +16,7 @@ class PlayButton(Button):
|
||||
handler = ResumeHandler(interaction, self.__bot)
|
||||
response = await handler.run()
|
||||
|
||||
if response.embed:
|
||||
if response and response.view is not None:
|
||||
await interaction.followup.send(embed=response.embed, view=response.view)
|
||||
elif response:
|
||||
await interaction.followup.send(embed=response.embed)
|
||||
|
||||
@ -16,5 +16,7 @@ class SkipButton(Button):
|
||||
handler = SkipHandler(interaction, self.__bot)
|
||||
response = await handler.run()
|
||||
|
||||
if response.embed:
|
||||
if response and response.view is not None:
|
||||
await interaction.followup.send(embed=response.embed, view=response.view)
|
||||
elif response:
|
||||
await interaction.followup.send(embed=response.embed)
|
||||
|
||||
@ -16,5 +16,7 @@ class SongsButton(Button):
|
||||
handler = QueueHandler(interaction, self.__bot)
|
||||
response = await handler.run()
|
||||
|
||||
if response.embed:
|
||||
if response and response.view is not None:
|
||||
await interaction.followup.send(embed=response.embed, view=response.view)
|
||||
elif response:
|
||||
await interaction.followup.send(embed=response.embed)
|
||||
|
||||
@ -16,5 +16,7 @@ class StopButton(Button):
|
||||
handler = StopHandler(interaction, self.__bot)
|
||||
response = await handler.run()
|
||||
|
||||
if response.embed:
|
||||
if response and response.view is not None:
|
||||
await interaction.followup.send(embed=response.embed, view=response.view)
|
||||
elif response:
|
||||
await interaction.followup.send(embed=response.embed)
|
||||
|
||||
@ -2,6 +2,7 @@ from abc import ABC, abstractmethod
|
||||
from Handlers.HandlerResponse import HandlerResponse
|
||||
from discord.ext.commands import Context
|
||||
from discord import Message
|
||||
from discord.ui import View
|
||||
from Music.VulkanBot import VulkanBot
|
||||
|
||||
|
||||
|
||||
@ -8,4 +8,4 @@ class EmbedCommandResponse(AbstractCommandResponse):
|
||||
|
||||
async def run(self) -> None:
|
||||
if self.response.embed:
|
||||
await self.context.send(embed=self.response.embed)
|
||||
await self.context.send(embed=self.response.embed, view=self.response.view)
|
||||
|
||||
29
UI/Views/EmptyView.py
Normal file
29
UI/Views/EmptyView.py
Normal file
@ -0,0 +1,29 @@
|
||||
from typing import List
|
||||
from discord import Button, Message
|
||||
from discord.ui import View
|
||||
from Config.Emojis import VEmojis
|
||||
from Music.VulkanBot import VulkanBot
|
||||
|
||||
emojis = VEmojis()
|
||||
|
||||
|
||||
class EmptyView(View):
|
||||
def __init__(self, bot: VulkanBot, buttons: List[Button], timeout: float = 6000):
|
||||
super().__init__(timeout=timeout)
|
||||
self.__bot = bot
|
||||
self.__message: Message = None
|
||||
|
||||
for button in buttons:
|
||||
self.add_item(button)
|
||||
|
||||
async def on_timeout(self) -> None:
|
||||
# Disable all itens and, if has the message, edit it
|
||||
try:
|
||||
self.disable_all_items()
|
||||
if self.__message is not None and isinstance(self.__message, Message):
|
||||
await self.__message.edit(view=self)
|
||||
except Exception as e:
|
||||
print(f'[ERROR EDITING MESSAGE] -> {e}')
|
||||
|
||||
def set_message(self, message: Message) -> None:
|
||||
self.__message = message
|
||||
Loading…
x
Reference in New Issue
Block a user