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 discord.ext.commands import Context
from Config.Exceptions import VulkanError from Config.Exceptions import VulkanError
from discord import Embed, Interaction from discord import Embed, Interaction
from discord.ui import View from UI.Views.AbstractView import AbstractView
class HandlerResponse: class HandlerResponse:
@ -22,7 +22,7 @@ class HandlerResponse:
return self.__embed return self.__embed
@property @property
def view(self) -> View: def view(self) -> AbstractView:
return self.__view return self.__view
def error(self) -> Union[VulkanError, None]: 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 Config.Exceptions import InvalidIndex
from Handlers.AbstractHandler import AbstractHandler from Handlers.AbstractHandler import AbstractHandler
from Handlers.HandlerResponse import HandlerResponse from Handlers.HandlerResponse import HandlerResponse
from Handlers.JumpMusicHandler import JumpMusicHandler
from Messages.MessagesCategory import MessagesCategory from Messages.MessagesCategory import MessagesCategory
from UI.Views.BasicView import BasicView from UI.Views.BasicView import BasicView
from Utils.Utils import Utils from Utils.Utils import Utils
@ -11,6 +12,7 @@ from Music.Playlist import Playlist
from typing import List, Union from typing import List, Union
from discord import Button, Interaction from discord import Button, Interaction
from UI.Buttons.EmptyButton import CallbackButton from UI.Buttons.EmptyButton import CallbackButton
from UI.Buttons.PlaylistDropdown import PlaylistDropdown
from Config.Emojis import VEmojis from Config.Emojis import VEmojis
@ -38,6 +40,12 @@ class QueueHandler(AbstractHandler):
processLock.release() # Release the Lock processLock.release() # Release the Lock
return HandlerResponse(self.ctx, embed) 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() songsPages = playlist.getSongsPages()
if pageNumber < 0 or pageNumber >= len(songsPages): if pageNumber < 0 or pageNumber >= len(songsPages):
embed = self.embeds.INVALID_INDEX() embed = self.embeds.INVALID_INDEX()
@ -45,16 +53,11 @@ class QueueHandler(AbstractHandler):
processLock.release() # Release the Lock processLock.release() # Release the Lock
return HandlerResponse(self.ctx, embed, error) 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 # Select the page in queue to be printed
songs = songsPages[pageNumber] songs = songsPages[pageNumber]
# Create view for this embed # Create view for this embed
buttons = self.__createViewButtons(songsPages, pageNumber) buttons = self.__createViewButtons(songsPages, pageNumber)
buttons.extend(self.__createViewJumpButtons(playlist))
queueView = BasicView(self.bot, buttons, self.config.QUEUE_VIEW_TIMEOUT) queueView = BasicView(self.bot, buttons, self.config.QUEUE_VIEW_TIMEOUT)
if playlist.isLoopingAll(): if playlist.isLoopingAll():
@ -96,3 +99,6 @@ class QueueHandler(AbstractHandler):
self.guild.id, MessagesCategory.QUEUE, "Next Page", pageNumber=nextPageNumber)) self.guild.id, MessagesCategory.QUEUE, "Next Page", pageNumber=nextPageNumber))
return buttons 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() embed = self.embeds.NOT_PLAYING()
return HandlerResponse(self.ctx, embed) 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 # Send a command to the player process to skip the music
command = VCommands(VCommandsType.SKIP, None) command = VCommands(VCommandsType.SKIP, None)
queue = processInfo.getQueueToPlayer() queue = processInfo.getQueueToPlayer()

View File

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

View File

@ -88,7 +88,7 @@ class Playlist:
self.__current = None self.__current = None
return None return None
self.__current = self.__queue.popleft() self.__current: Song = self.__queue.popleft()
return self.__current return self.__current
def prev_song(self) -> Song: 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 typing import Awaitable
from Config.Emojis import VEmojis from Config.Emojis import VEmojis
from discord import ButtonStyle, Interaction, Message, TextChannel from discord import ButtonStyle, Interaction, Message, TextChannel
from discord.ui import Button from discord.ui import Button, View
from Handlers.HandlerResponse import HandlerResponse from Handlers.HandlerResponse import HandlerResponse
from Messages.MessagesCategory import MessagesCategory from Messages.MessagesCategory import MessagesCategory
from Messages.MessagesManager import MessagesManager from Messages.MessagesManager import MessagesManager
@ -21,6 +21,7 @@ class CallbackButton(Button):
self.__args = args self.__args = args
self.__kwargs = kwargs self.__kwargs = kwargs
self.__callback = cb self.__callback = cb
self.__view: View = None
async def callback(self, interaction: Interaction) -> None: async def callback(self, interaction: Interaction) -> None:
"""Callback to when Button is clicked""" """Callback to when Button is clicked"""
@ -29,10 +30,19 @@ class CallbackButton(Button):
response: HandlerResponse = await self.__callback(*self.__args, **self.__kwargs) response: HandlerResponse = await self.__callback(*self.__args, **self.__kwargs)
message = None
if response and response.view is not None: if response and response.view is not None:
message: Message = await self.__channel.send(embed=response.embed, view=response.view) 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) message: Message = await self.__channel.send(embed=response.embed)
# Clear the last sended message in this category and add the new one # 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 Config.Emojis import VEmojis
from discord import ButtonStyle, Interaction, Message, TextChannel from discord import ButtonStyle, Interaction, Message, TextChannel
from discord.ui import Button from discord.ui import Button, View
from Handlers.HandlerResponse import HandlerResponse from Handlers.HandlerResponse import HandlerResponse
from Messages.MessagesCategory import MessagesCategory from Messages.MessagesCategory import MessagesCategory
from Music.VulkanBot import VulkanBot from Music.VulkanBot import VulkanBot
@ -21,6 +21,7 @@ class HandlerButton(Button):
self.__args = args self.__args = args
self.__kwargs = kwargs self.__kwargs = kwargs
self.__handlerClass = handler self.__handlerClass = handler
self.__view: View = None
async def callback(self, interaction: Interaction) -> None: async def callback(self, interaction: Interaction) -> None:
"""Callback to when Button is clicked""" """Callback to when Button is clicked"""
@ -31,10 +32,19 @@ class HandlerButton(Button):
handler = self.__handlerClass(interaction, self.__bot) handler = self.__handlerClass(interaction, self.__bot)
response: HandlerResponse = await handler.run(*self.__args, **self.__kwargs) response: HandlerResponse = await handler.run(*self.__args, **self.__kwargs)
message = None
if response and response.view is not None: if response and response.view is not None:
message: Message = await self.__channel.send(embed=response.embed, view=response.view) 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) message: Message = await self.__channel.send(embed=response.embed)
# Clear the last category sended message and add the new one # 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 typing import List
from discord import Button, Message from discord import Message
from discord.ui import View from discord.ui import View
from Config.Emojis import VEmojis from Config.Emojis import VEmojis
from Music.VulkanBot import VulkanBot from Music.VulkanBot import VulkanBot
from UI.Views.AbstractView import AbstractView
from UI.Buttons.AbstractItem import AbstractItem
emojis = VEmojis() emojis = VEmojis()
class BasicView(View): class BasicView(View, AbstractView):
"""View that receives buttons to hold, in timeout disable buttons""" """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) super().__init__(timeout=timeout)
self.__bot = bot self.__bot = bot
self.__message: Message = None self.__message: Message = None
for button in buttons: 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) self.add_item(button)
async def on_timeout(self) -> None: async def on_timeout(self) -> None:
@ -29,3 +33,7 @@ class BasicView(View):
def set_message(self, message: Message) -> None: def set_message(self, message: Message) -> None:
self.__message = message self.__message = message
async def update(self):
if self.__message is not None:
await self.__message.edit(view=self)