Changing View in Queue message and creating new handler to jump to music

This commit is contained in:
Rafael Vargas 2022-08-07 20:03:13 -04:00
parent 15f8ea7cb2
commit 2d27a2f080
12 changed files with 244 additions and 24 deletions

View File

@ -2,7 +2,7 @@ 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
from UI.Views.AbstractView import AbstractView
class HandlerResponse:
@ -22,7 +22,7 @@ class HandlerResponse:
return self.__embed
@property
def view(self) -> View:
def view(self) -> AbstractView:
return self.__view
def error(self) -> Union[VulkanError, None]:

View File

@ -0,0 +1,80 @@
from typing import Union
from Config.Exceptions import BadCommandUsage, InvalidInput, NumberRequired, UnknownError, VulkanError
from Handlers.AbstractHandler import AbstractHandler
from discord.ext.commands import Context
from discord import Interaction
from Handlers.HandlerResponse import HandlerResponse
from Music.Playlist import Playlist
from Music.VulkanBot import VulkanBot
from Parallelism.Commands import VCommands, VCommandsType
class JumpMusicHandler(AbstractHandler):
"""Move a music from a specific position and play it directly"""
def __init__(self, ctx: Union[Context, Interaction], bot: VulkanBot) -> None:
super().__init__(ctx, bot)
async def run(self, musicPos: str) -> HandlerResponse:
processManager = self.config.getProcessManager()
processInfo = processManager.getRunningPlayerInfo(self.guild)
if not processInfo:
embed = self.embeds.NOT_PLAYING()
error = BadCommandUsage()
return HandlerResponse(self.ctx, embed, error)
processLock = processInfo.getLock()
acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
if acquired:
# Try to convert input to int
error = self.__validateInput(musicPos)
if error:
embed = self.embeds.ERROR_EMBED(error.message)
processLock.release()
return HandlerResponse(self.ctx, embed, error)
# Sanitize the input
playlist: Playlist = processInfo.getPlaylist()
musicPos = self.__sanitizeInput(playlist, musicPos)
# Validate the position
if not playlist.validate_position(musicPos):
error = InvalidInput()
embed = self.embeds.PLAYLIST_RANGE_ERROR()
processLock.release()
return HandlerResponse(self.ctx, embed, error)
try:
# Move the selected song
playlist.move_songs(musicPos, 1)
# Send a command to the player to skip the music
command = VCommands(VCommandsType.SKIP, None)
queue = processInfo.getQueueToPlayer()
queue.put(command)
processLock.release()
return HandlerResponse(self.ctx)
except:
# Release the acquired Lock
processLock.release()
embed = self.embeds.ERROR_MOVING()
error = UnknownError()
return HandlerResponse(self.ctx, embed, error)
else:
processManager.resetProcess(self.guild, self.ctx)
embed = self.embeds.PLAYER_RESTARTED()
return HandlerResponse(self.ctx, embed)
def __validateInput(self, position: str) -> Union[VulkanError, None]:
try:
position = int(position)
except:
return NumberRequired(self.messages.ERROR_NUMBER)
def __sanitizeInput(self, playlist: Playlist, position: int) -> int:
position = int(position)
if position == -1:
position = len(playlist.getSongs())
return position

View File

@ -2,6 +2,7 @@ from discord.ext.commands import Context
from Config.Exceptions import InvalidIndex
from Handlers.AbstractHandler import AbstractHandler
from Handlers.HandlerResponse import HandlerResponse
from Handlers.JumpMusicHandler import JumpMusicHandler
from Messages.MessagesCategory import MessagesCategory
from UI.Views.BasicView import BasicView
from Utils.Utils import Utils
@ -11,6 +12,7 @@ from Music.Playlist import Playlist
from typing import List, Union
from discord import Button, Interaction
from UI.Buttons.EmptyButton import CallbackButton
from UI.Buttons.PlaylistDropdown import PlaylistDropdown
from Config.Emojis import VEmojis
@ -38,6 +40,12 @@ class QueueHandler(AbstractHandler):
processLock.release() # Release the Lock
return HandlerResponse(self.ctx, embed)
allSongs = playlist.getSongs()
if len(allSongs) == 0:
embed = self.embeds.EMPTY_QUEUE()
processLock.release() # Release the Lock
return HandlerResponse(self.ctx, embed)
songsPages = playlist.getSongsPages()
if pageNumber < 0 or pageNumber >= len(songsPages):
embed = self.embeds.INVALID_INDEX()
@ -45,16 +53,11 @@ class QueueHandler(AbstractHandler):
processLock.release() # Release the Lock
return HandlerResponse(self.ctx, embed, error)
allSongs = playlist.getSongs()
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)
buttons.extend(self.__createViewJumpButtons(playlist))
queueView = BasicView(self.bot, buttons, self.config.QUEUE_VIEW_TIMEOUT)
if playlist.isLoopingAll():
@ -96,3 +99,6 @@ class QueueHandler(AbstractHandler):
self.guild.id, MessagesCategory.QUEUE, "Next Page", pageNumber=nextPageNumber))
return buttons
def __createViewJumpButtons(self, playlist: Playlist) -> List[Button]:
return [PlaylistDropdown(self.bot, JumpMusicHandler, playlist, self.ctx.channel, self.guild.id, MessagesCategory.PLAYER)]

View File

@ -26,12 +26,6 @@ class SkipHandler(AbstractHandler):
embed = self.embeds.NOT_PLAYING()
return HandlerResponse(self.ctx, embed)
playlist = processInfo.getPlaylist()
if playlist.isLoopingOne():
embed = self.embeds.ERROR_DUE_LOOP_ONE_ON()
error = BadCommandUsage()
return HandlerResponse(self.ctx, embed, error)
# Send a command to the player process to skip the music
command = VCommands(VCommandsType.SKIP, None)
queue = processInfo.getQueueToPlayer()

View File

@ -8,11 +8,15 @@ class EmbedCommandResponse(AbstractCommandResponse):
super().__init__(response, category)
async def run(self, deleteLast: bool = True) -> None:
message = None
if self.response.embed and self.response.view:
message = await self.context.send(embed=self.response.embed, view=self.response.view)
# Set the view to contain the sended message
self.response.view.set_message(message)
elif self.response.embed:
message = await self.context.send(embed=self.response.embed)
if message:
# Only delete the previous message if this is not error and not forbidden by method caller
if deleteLast and self.response.success:
await self.manager.addMessageAndClearPrevious(self.context.guild.id, self.category, message)

View File

@ -88,7 +88,7 @@ class Playlist:
self.__current = None
return None
self.__current = self.__queue.popleft()
self.__current: Song = self.__queue.popleft()
return self.__current
def prev_song(self) -> Song:

View File

@ -0,0 +1,12 @@
from abc import ABC, abstractmethod
from discord.ui import Item, View
class AbstractItem(ABC, Item):
@abstractmethod
def set_view(self, view: View):
pass
@abstractmethod
def get_view(self) -> View:
pass

View File

@ -1,7 +1,7 @@
from typing import Awaitable
from Config.Emojis import VEmojis
from discord import ButtonStyle, Interaction, Message, TextChannel
from discord.ui import Button
from discord.ui import Button, View
from Handlers.HandlerResponse import HandlerResponse
from Messages.MessagesCategory import MessagesCategory
from Messages.MessagesManager import MessagesManager
@ -21,6 +21,7 @@ class CallbackButton(Button):
self.__args = args
self.__kwargs = kwargs
self.__callback = cb
self.__view: View = None
async def callback(self, interaction: Interaction) -> None:
"""Callback to when Button is clicked"""
@ -29,10 +30,19 @@ class CallbackButton(Button):
response: HandlerResponse = await self.__callback(*self.__args, **self.__kwargs)
message = None
if response and response.view is not None:
message: Message = await self.__channel.send(embed=response.embed, view=response.view)
else:
response.view.set_message(message)
elif response.embed:
message: Message = await self.__channel.send(embed=response.embed)
# Clear the last sended message in this category and add the new one
await self.__messagesManager.addMessageAndClearPrevious(self.__guildID, self.__category, message)
if message:
await self.__messagesManager.addMessageAndClearPrevious(self.__guildID, self.__category, message)
def set_view(self, view: View):
self.__view = view
def get_view(self) -> View:
return self.__view

View File

@ -1,6 +1,6 @@
from Config.Emojis import VEmojis
from discord import ButtonStyle, Interaction, Message, TextChannel
from discord.ui import Button
from discord.ui import Button, View
from Handlers.HandlerResponse import HandlerResponse
from Messages.MessagesCategory import MessagesCategory
from Music.VulkanBot import VulkanBot
@ -21,6 +21,7 @@ class HandlerButton(Button):
self.__args = args
self.__kwargs = kwargs
self.__handlerClass = handler
self.__view: View = None
async def callback(self, interaction: Interaction) -> None:
"""Callback to when Button is clicked"""
@ -31,10 +32,19 @@ class HandlerButton(Button):
handler = self.__handlerClass(interaction, self.__bot)
response: HandlerResponse = await handler.run(*self.__args, **self.__kwargs)
message = None
if response and response.view is not None:
message: Message = await self.__channel.send(embed=response.embed, view=response.view)
else:
response.view.set_message(message)
elif response.embed:
message: Message = await self.__channel.send(embed=response.embed)
# Clear the last category sended message and add the new one
await self.__messagesManager.addMessageAndClearPrevious(self.__guildID, self.__category, message)
if message:
await self.__messagesManager.addMessageAndClearPrevious(self.__guildID, self.__category, message)
def set_view(self, view: View):
self.__view = view
def get_view(self) -> View:
return self.__view

View File

@ -0,0 +1,86 @@
import asyncio
from typing import List
from discord import Interaction, Message, TextChannel, SelectOption
from discord.ui import Select, View
from Handlers.HandlerResponse import HandlerResponse
from Messages.MessagesCategory import MessagesCategory
from Messages.MessagesManager import MessagesManager
from Music.VulkanBot import VulkanBot
from Handlers.AbstractHandler import AbstractHandler
from UI.Buttons.AbstractItem import AbstractItem
from UI.Views.AbstractView import AbstractView
from Music.Playlist import Playlist
class PlaylistDropdown(Select, AbstractItem):
"""Receives n elements to put in drop down and return the selected, pass the index value to a handler"""
def __init__(self, bot: VulkanBot, handler: type[AbstractHandler], playlist: Playlist, textChannel: TextChannel, guildID: int, category: MessagesCategory):
songs = list(playlist.getSongs())
values = [str(x) for x in range(1, len(songs) + 1)]
# Get the title of each of the 20 first songs, library doesn't accept more
songsNames = [song.title[:80] for song in songs[:20]]
selectOptions: List[SelectOption] = []
for x in range(len(songsNames)):
selectOptions.append(SelectOption(label=songsNames[x], value=values[x]))
super().__init__(placeholder="Select one music to play now, may be overdue",
min_values=1, max_values=1, options=selectOptions)
self.__playlist = playlist
self.__channel = textChannel
self.__guildID = guildID
self.__category = category
self.__handlerClass = handler
self.__messagesManager = MessagesManager()
self.__bot = bot
self.__view: AbstractView = None
async def callback(self, interaction: Interaction) -> None:
"""Callback to when the selection is selected"""
await interaction.response.defer()
# Execute the handler passing the value selected
handler = self.__handlerClass(interaction, self.__bot)
response: HandlerResponse = await handler.run(self.values[0])
message = None
if response and response.view is not None:
message: Message = await self.__channel.send(embed=response.embed, view=response.view)
elif response.embed:
message: Message = await self.__channel.send(embed=response.embed)
# Clear the last sended message in this category and add the new one
if message:
await self.__messagesManager.addMessageAndClearPrevious(self.__guildID, self.__category, message)
# Extreme ugly way to wait for the player process to actually retrieve the next song
await asyncio.sleep(2)
await self.__update()
async def __update(self):
songs = list(self.__playlist.getSongs())
values = [str(x) for x in range(1, len(songs) + 1)]
# Get the title of each of the 20 first songs, library doesn't accept more
songsNames = [song.title[:80] for song in songs[:20]]
selectOptions: List[SelectOption] = []
for x in range(len(songsNames)):
selectOptions.append(SelectOption(label=songsNames[x], value=values[x]))
self.options = selectOptions
if self.__view is not None:
await self.__view.update()
def set_view(self, view: View):
self.__view = view
def get_view(self) -> View:
return self.__view

10
UI/Views/AbstractView.py Normal file
View File

@ -0,0 +1,10 @@
from abc import ABC, abstractmethod
class AbstractView(ABC):
@abstractmethod
async def update(self) -> None:
pass
def set_message(self, message) -> None:
pass

View File

@ -1,21 +1,25 @@
from typing import List
from discord import Button, Message
from discord import Message
from discord.ui import View
from Config.Emojis import VEmojis
from Music.VulkanBot import VulkanBot
from UI.Views.AbstractView import AbstractView
from UI.Buttons.AbstractItem import AbstractItem
emojis = VEmojis()
class BasicView(View):
class BasicView(View, AbstractView):
"""View that receives buttons to hold, in timeout disable buttons"""
def __init__(self, bot: VulkanBot, buttons: List[Button], timeout: float = 6000):
def __init__(self, bot: VulkanBot, buttons: List[AbstractItem], timeout: float = 6000):
super().__init__(timeout=timeout)
self.__bot = bot
self.__message: Message = None
for button in buttons:
# Set the buttons to have a instance of the view that contains them
button.set_view(self)
self.add_item(button)
async def on_timeout(self) -> None:
@ -29,3 +33,7 @@ class BasicView(View):
def set_message(self, message: Message) -> None:
self.__message = message
async def update(self):
if self.__message is not None:
await self.__message.edit(view=self)