Merge pull request #26 from RafaelSolVargas/jumpMusic

Upgrading Song Queue message
This commit is contained in:
Rafael Vargas 2022-08-16 19:36:17 -03:00 committed by GitHub
commit 5f60c12179
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 796 additions and 403 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

BIN
Assets/playermenu.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
Assets/queuemessage.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

BIN
Assets/vulkancommands.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View File

@ -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

View File

@ -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,
@ -261,6 +277,41 @@ class VEmbeds:
)
return embed
def PLAYER_RESUMED(self) -> Embed:
embed = Embed(
title=self.__messages.SONG_RESUMED,
colour=self.__colors.BLUE
)
return embed
def SKIPPING_SONG(self) -> Embed:
embed = Embed(
title=self.__messages.SONG_SKIPPED,
colour=self.__colors.BLUE
)
return embed
def STOPPING_PLAYER(self) -> Embed:
embed = Embed(
title=self.__messages.STOPPING,
colour=self.__colors.BLUE
)
return embed
def RETURNING_SONG(self) -> Embed:
embed = Embed(
title=self.__messages.RETURNING_SONG,
colour=self.__colors.BLUE
)
return embed
def PLAYER_PAUSED(self) -> Embed:
embed = Embed(
title=self.__messages.SONG_PAUSED,
colour=self.__colors.BLUE
)
return embed
def NOT_PREVIOUS_SONG(self) -> Embed:
embed = Embed(
title=self.__messages.SONG_PLAYER,

View File

@ -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)

View File

@ -26,6 +26,9 @@ class Messages(Singleton):
self.ALL_SONGS_LOOPING = f'{self.__emojis.MUSIC} Looping All Songs'
self.SONG_PAUSED = f'{self.__emojis.PAUSE} Song paused'
self.SONG_RESUMED = f'{self.__emojis.PLAY} Song playing'
self.SONG_SKIPPED = f'{self.__emojis.SKIP} Song skipped'
self.RETURNING_SONG = f'{self.__emojis.BACK} Playing previous song'
self.STOPPING = f'{self.__emojis.STOP} Player Stopped'
self.EMPTY_QUEUE = f'{self.__emojis.QUEUE} Song queue is empty, use {configs.BOT_PREFIX}play to add new songs'
self.SONG_DOWNLOADING = f'{self.__emojis.DOWNLOADING} Downloading...'
@ -64,6 +67,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'

View File

@ -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
@ -15,10 +17,12 @@ from Handlers.ResumeHandler import ResumeHandler
from Handlers.HistoryHandler import HistoryHandler
from Handlers.QueueHandler import QueueHandler
from Handlers.LoopHandler import LoopHandler
from UI.Responses.EmoteCogResponse import EmoteCommandResponse
from UI.Responses.EmbedCogResponse import EmbedCommandResponse
from Messages.MessagesCategory import MessagesCategory
from Messages.Responses.EmoteCogResponse import EmoteCommandResponse
from Messages.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 +37,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'])
@ -42,21 +47,37 @@ class MusicCog(Cog):
response = await controller.run(args)
if response is not None:
view1 = EmbedCommandResponse(response)
view2 = EmoteCommandResponse(response)
await view1.run()
await view2.run()
cogResponser1 = EmbedCommandResponse(response, MessagesCategory.PLAYER)
cogResponser2 = EmoteCommandResponse(response, MessagesCategory.PLAYER)
await cogResponser1.run()
await cogResponser2.run()
except Exception as e:
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:
pageNumber = " ".join(args)
controller = QueueHandler(ctx, self.__bot)
response = await controller.run()
view2 = EmbedCommandResponse(response)
await view2.run()
if pageNumber == "":
response = await controller.run()
else:
pageNumber = int(pageNumber)
pageNumber -= 1 # Change index 1 to 0
response = await controller.run(pageNumber)
cogResponser = EmbedCommandResponse(response, MessagesCategory.QUEUE)
await cogResponser.run()
except ValueError as e:
# Draft a Handler Response to pass to cogResponser
error = InvalidInput()
embed = self.__embeds.INVALID_ARGUMENTS()
response = HandlerResponse(ctx, embed, error)
cogResponser = EmbedCommandResponse(response, MessagesCategory.QUEUE)
await cogResponser.run(deleteLast=False)
except Exception as e:
print(f'[ERROR IN COG] -> {e}')
@ -66,12 +87,10 @@ class MusicCog(Cog):
controller = SkipHandler(ctx, self.__bot)
response = await controller.run()
if response.success:
view = EmoteCommandResponse(response)
else:
view = EmbedCommandResponse(response)
await view.run()
cogResponser1 = EmbedCommandResponse(response, MessagesCategory.PLAYER)
cogResponser2 = EmoteCommandResponse(response, MessagesCategory.PLAYER)
await cogResponser1.run()
await cogResponser2.run()
except Exception as e:
print(f'[ERROR IN COG] -> {e}')
@ -81,12 +100,10 @@ class MusicCog(Cog):
controller = StopHandler(ctx, self.__bot)
response = await controller.run()
if response.success:
view = EmoteCommandResponse(response)
else:
view = EmbedCommandResponse(response)
await view.run()
cogResponser1 = EmbedCommandResponse(response, MessagesCategory.PLAYER)
cogResponser2 = EmoteCommandResponse(response, MessagesCategory.PLAYER)
await cogResponser1.run()
await cogResponser2.run()
except Exception as e:
print(f'[ERROR IN COG] -> {e}')
@ -96,10 +113,10 @@ class MusicCog(Cog):
controller = PauseHandler(ctx, self.__bot)
response = await controller.run()
view1 = EmoteCommandResponse(response)
view2 = EmbedCommandResponse(response)
await view1.run()
await view2.run()
cogResponser1 = EmoteCommandResponse(response, MessagesCategory.PLAYER)
cogResponser2 = EmbedCommandResponse(response, MessagesCategory.PLAYER)
await cogResponser1.run()
await cogResponser2.run()
except Exception as e:
print(f'[ERROR IN COG] -> {e}')
@ -109,10 +126,10 @@ class MusicCog(Cog):
controller = ResumeHandler(ctx, self.__bot)
response = await controller.run()
view1 = EmoteCommandResponse(response)
view2 = EmbedCommandResponse(response)
await view1.run()
await view2.run()
cogResponser1 = EmoteCommandResponse(response, MessagesCategory.PLAYER)
cogResponser2 = EmbedCommandResponse(response, MessagesCategory.PLAYER)
await cogResponser1.run()
await cogResponser2.run()
except Exception as e:
print(f'[ERROR IN COG] -> {e}')
@ -123,10 +140,10 @@ class MusicCog(Cog):
response = await controller.run()
if response is not None:
view1 = EmbedCommandResponse(response)
view2 = EmoteCommandResponse(response)
await view1.run()
await view2.run()
cogResponser1 = EmbedCommandResponse(response, MessagesCategory.PLAYER)
cogResponser2 = EmoteCommandResponse(response, MessagesCategory.PLAYER)
await cogResponser1.run()
await cogResponser2.run()
except Exception as e:
print(f'[ERROR IN COG] -> {e}')
@ -136,10 +153,10 @@ class MusicCog(Cog):
controller = HistoryHandler(ctx, self.__bot)
response = await controller.run()
view1 = EmbedCommandResponse(response)
view2 = EmoteCommandResponse(response)
await view1.run()
await view2.run()
cogResponser1 = EmbedCommandResponse(response, MessagesCategory.HISTORY)
cogResponser2 = EmoteCommandResponse(response, MessagesCategory.HISTORY)
await cogResponser1.run()
await cogResponser2.run()
except Exception as e:
print(f'[ERROR IN COG] -> {e}')
@ -149,10 +166,10 @@ class MusicCog(Cog):
controller = LoopHandler(ctx, self.__bot)
response = await controller.run(args)
view1 = EmoteCommandResponse(response)
view2 = EmbedCommandResponse(response)
await view1.run()
await view2.run()
cogResponser1 = EmoteCommandResponse(response, MessagesCategory.LOOP)
cogResponser2 = EmbedCommandResponse(response, MessagesCategory.LOOP)
await cogResponser1.run()
await cogResponser2.run()
except Exception as e:
print(f'[ERROR IN COG] -> {e}')
@ -162,8 +179,10 @@ class MusicCog(Cog):
controller = ClearHandler(ctx, self.__bot)
response = await controller.run()
view = EmoteCommandResponse(response)
await view.run()
cogResponser1 = EmbedCommandResponse(response, MessagesCategory.PLAYER)
cogResponser2 = EmoteCommandResponse(response, MessagesCategory.PLAYER)
await cogResponser1.run()
await cogResponser2.run()
except Exception as e:
print(f'[ERROR IN COG] -> {e}')
@ -173,10 +192,10 @@ class MusicCog(Cog):
controller = NowPlayingHandler(ctx, self.__bot)
response = await controller.run()
view1 = EmbedCommandResponse(response)
view2 = EmoteCommandResponse(response)
await view1.run()
await view2.run()
cogResponser1 = EmbedCommandResponse(response, MessagesCategory.NOW_PLAYING)
cogResponser2 = EmoteCommandResponse(response, MessagesCategory.NOW_PLAYING)
await cogResponser1.run()
await cogResponser2.run()
except Exception as e:
print(f'[ERROR IN COG] -> {e}')
@ -186,10 +205,10 @@ class MusicCog(Cog):
controller = ShuffleHandler(ctx, self.__bot)
response = await controller.run()
view1 = EmbedCommandResponse(response)
view2 = EmoteCommandResponse(response)
await view1.run()
await view2.run()
cogResponser1 = EmbedCommandResponse(response, MessagesCategory.PLAYER)
cogResponser2 = EmoteCommandResponse(response, MessagesCategory.PLAYER)
await cogResponser1.run()
await cogResponser2.run()
except Exception as e:
print(f'[ERROR IN COG] -> {e}')
@ -199,10 +218,10 @@ class MusicCog(Cog):
controller = MoveHandler(ctx, self.__bot)
response = await controller.run(pos1, pos2)
view1 = EmbedCommandResponse(response)
view2 = EmoteCommandResponse(response)
await view1.run()
await view2.run()
cogResponser1 = EmbedCommandResponse(response, MessagesCategory.MANAGING_QUEUE)
cogResponser2 = EmoteCommandResponse(response, MessagesCategory.MANAGING_QUEUE)
await cogResponser1.run()
await cogResponser2.run()
except Exception as e:
print(f'[ERROR IN COG] -> {e}')
@ -212,10 +231,10 @@ class MusicCog(Cog):
controller = RemoveHandler(ctx, self.__bot)
response = await controller.run(position)
view1 = EmbedCommandResponse(response)
view2 = EmoteCommandResponse(response)
await view1.run()
await view2.run()
cogResponser1 = EmbedCommandResponse(response, MessagesCategory.MANAGING_QUEUE)
cogResponser2 = EmoteCommandResponse(response, MessagesCategory.MANAGING_QUEUE)
await cogResponser1.run()
await cogResponser2.run()
except Exception as e:
print(f'[ERROR IN COG] -> {e}')
@ -225,10 +244,10 @@ class MusicCog(Cog):
controller = ResetHandler(ctx, self.__bot)
response = await controller.run()
view1 = EmbedCommandResponse(response)
view2 = EmoteCommandResponse(response)
await view1.run()
await view2.run()
cogResponser1 = EmbedCommandResponse(response, MessagesCategory.PLAYER)
cogResponser2 = EmoteCommandResponse(response, MessagesCategory.PLAYER)
await cogResponser1.run()
await cogResponser2.run()
except Exception as e:
print(f'[ERROR IN COG] -> {e}')

View File

@ -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 UI.Views.AbstractView import AbstractView
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) -> AbstractView:
return self.__view
def error(self) -> Union[VulkanError, None]:
return self.__error

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

@ -25,7 +25,8 @@ class PauseHandler(AbstractHandler):
queue = processInfo.getQueueToPlayer()
queue.put(command)
return HandlerResponse(self.ctx)
embed = self.embeds.PLAYER_PAUSED()
return HandlerResponse(self.ctx, embed)
else:
embed = self.embeds.NOT_PLAYING()
return HandlerResponse(self.ctx, embed)

View File

@ -45,7 +45,9 @@ class PrevHandler(AbstractHandler):
prevCommand = VCommands(VCommandsType.PREV, self.author.voice.channel.id)
queue = processInfo.getQueueToPlayer()
queue.put(prevCommand)
return HandlerResponse(self.ctx)
embed = self.embeds.RETURNING_SONG()
return HandlerResponse(self.ctx, embed)
def __user_connected(self) -> bool:
if self.author.voice:

View File

@ -1,19 +1,26 @@
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 Handlers.JumpMusicHandler import JumpMusicHandler
from Messages.MessagesCategory import MessagesCategory
from UI.Views.BasicView import BasicView
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.CallbackButton import CallbackButton
from UI.Buttons.PlaylistDropdown import PlaylistDropdown
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 +32,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 +40,26 @@ class QueueHandler(AbstractHandler):
processLock.release() # Release the Lock
return HandlerResponse(self.ctx, embed)
songs_preload = playlist.getSongsToPreload()
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)
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)
# 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():
title = self.messages.ALL_SONGS_LOOPING
else:
@ -49,17 +69,36 @@ 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(CallbackButton(self.bot, self.run, VEmojis().BACK, self.ctx.channel,
self.guild.id, MessagesCategory.QUEUE, "Prev Page", pageNumber=prevPageNumber))
if pageNumber < len(songsPages) - 1:
nextPageNumber = pageNumber + 1
buttons.append(CallbackButton(self.bot, self.run, VEmojis().SKIP, self.ctx.channel,
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

@ -25,7 +25,8 @@ class ResumeHandler(AbstractHandler):
queue = processInfo.getQueueToPlayer()
queue.put(command)
return HandlerResponse(self.ctx)
embed = self.embeds.PLAYER_RESUMED()
return HandlerResponse(self.ctx, embed)
else:
embed = self.embeds.NOT_PLAYING()
return HandlerResponse(self.ctx, embed)

View File

@ -26,18 +26,13 @@ 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()
queue.put(command)
return HandlerResponse(self.ctx)
embed = self.embeds.SKIPPING_SONG()
return HandlerResponse(self.ctx, embed)
else:
embed = self.embeds.NOT_PLAYING()
return HandlerResponse(self.ctx, embed)

View File

@ -25,7 +25,8 @@ class StopHandler(AbstractHandler):
queue = processInfo.getQueueToPlayer()
queue.put(command)
return HandlerResponse(self.ctx)
embed = self.embeds.STOPPING_PLAYER()
return HandlerResponse(self.ctx, embed)
else:
embed = self.embeds.NOT_PLAYING()
return HandlerResponse(self.ctx, embed)

View File

@ -0,0 +1,11 @@
from enum import Enum
class MessagesCategory(Enum):
QUEUE = 1
HISTORY = 2
LOOP = 3
NOW_PLAYING = 4
PLAYER = 5
MANAGING_QUEUE = 6
OTHERS = 7

View File

@ -0,0 +1,80 @@
from typing import Dict, List
from discord import Message
from Config.Singleton import Singleton
from UI.Views.AbstractView import AbstractView
from Messages.MessagesCategory import MessagesCategory
class MessagesManager(Singleton):
def __init__(self) -> None:
if not super().created:
# For each guild, and for each category, there will be a list of messages
self.__guildsMessages: Dict[int, Dict[MessagesCategory, List[Message]]] = {}
# Will, for each message, store the AbstractView that controls it
self.__messagesViews: Dict[Message, AbstractView] = {}
def addMessage(self, guildID: int, category: MessagesCategory, message: Message, view: AbstractView = None) -> None:
if message is None:
return
# If guild not exists create Dict
if guildID not in self.__guildsMessages.keys():
self.__guildsMessages[guildID] = {}
# If category not in guild yet, add
if category not in self.__guildsMessages[guildID].keys():
self.__guildsMessages[guildID][category] = []
sendedMessages = self.__guildsMessages[guildID][category]
if view is not None and isinstance(view, AbstractView):
self.__messagesViews[message] = view
sendedMessages.append(message)
async def addMessageAndClearPrevious(self, guildID: int, category: MessagesCategory, message: Message, view: AbstractView = None) -> None:
if message is None:
return
# If guild not exists create Dict
if guildID not in self.__guildsMessages.keys():
self.__guildsMessages[guildID] = {}
# If category not in guild yet, add
if category not in self.__guildsMessages[guildID].keys():
self.__guildsMessages[guildID][category] = []
sendedMessages = self.__guildsMessages[guildID][category]
# Delete sended all messages of this category
for previousMessage in sendedMessages:
await self.__deleteMessage(previousMessage)
# Create a new list with only the new message
self.__guildsMessages[guildID][category] = [message]
# Store the view of this message
if view is not None and isinstance(view, AbstractView):
self.__messagesViews[message] = view
async def clearMessagesOfCategory(self, guildID: int, category: MessagesCategory) -> None:
sendedMessages = self.__guildsMessages[guildID][category]
for message in sendedMessages:
self.__deleteMessage(message)
async def clearMessagesOfGuild(self, guildID: int) -> None:
categoriesMessages = self.__guildsMessages[guildID]
for category in categoriesMessages.keys():
for message in categoriesMessages[category]:
self.__deleteMessage(message)
async def __deleteMessage(self, message: Message) -> None:
try:
# If there is a view for this message delete the key
if message in self.__messagesViews.keys():
messageView = self.__messagesViews.pop(message)
messageView.stopView()
del messageView
await message.delete()
except Exception as e:
print(f'[ERROR DELETING MESSAGE] -> {e}')
pass

View File

@ -2,12 +2,16 @@ from abc import ABC, abstractmethod
from Handlers.HandlerResponse import HandlerResponse
from discord.ext.commands import Context
from discord import Message
from Messages.MessagesCategory import MessagesCategory
from Messages.MessagesManager import MessagesManager
from Music.VulkanBot import VulkanBot
class AbstractCommandResponse(ABC):
def __init__(self, response: HandlerResponse) -> None:
def __init__(self, response: HandlerResponse, category: MessagesCategory) -> None:
self.__messagesManager = MessagesManager()
self.__response: HandlerResponse = response
self.__category: MessagesCategory = category
self.__context: Context = response.ctx
self.__message: Message = response.ctx.message
self.__bot: VulkanBot = response.ctx.bot
@ -16,6 +20,10 @@ class AbstractCommandResponse(ABC):
def response(self) -> HandlerResponse:
return self.__response
@property
def category(self) -> MessagesCategory:
return self.__category
@property
def bot(self) -> VulkanBot:
return self.__bot
@ -28,6 +36,10 @@ class AbstractCommandResponse(ABC):
def context(self) -> Context:
return self.__context
@property
def manager(self) -> MessagesManager:
return self.__messagesManager
@abstractmethod
async def run(self) -> None:
async def run(self, deleteLast: bool = True) -> None:
pass

View File

@ -0,0 +1,24 @@
from Messages.Responses.AbstractCogResponse import AbstractCommandResponse
from Handlers.HandlerResponse import HandlerResponse
from Messages.MessagesCategory import MessagesCategory
class EmbedCommandResponse(AbstractCommandResponse):
def __init__(self, response: HandlerResponse, category: MessagesCategory) -> None:
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, self.response.view)
else:
self.manager.addMessage(self.context.guild.id, self.category, message)

View File

@ -1,15 +1,16 @@
from Config.Emojis import VEmojis
from UI.Responses.AbstractCogResponse import AbstractCommandResponse
from Messages.Responses.AbstractCogResponse import AbstractCommandResponse
from Handlers.HandlerResponse import HandlerResponse
from Messages.MessagesCategory import MessagesCategory
class EmoteCommandResponse(AbstractCommandResponse):
def __init__(self, response: HandlerResponse) -> None:
super().__init__(response)
def __init__(self, response: HandlerResponse, category: MessagesCategory) -> None:
super().__init__(response, category)
self.__emojis = VEmojis()
async def run(self) -> None:
async def run(self, deleteLast: bool = True) -> None:
if self.response.success:
await self.message.add_reaction(self.__emojis.SUCCESS)
else:

View File

@ -1,65 +0,0 @@
from typing import List
from discord import Embed, Message, TextChannel
from Music.VulkanBot import VulkanBot
from Parallelism.ProcessInfo import ProcessInfo
from Config.Configs import VConfigs
from Config.Messages import Messages
from Music.Song import Song
from Config.Embeds import VEmbeds
from UI.Views.PlayerView import PlayerView
class MessagesController:
def __init__(self, bot: VulkanBot) -> None:
self.__bot = bot
self.__previousMessages = []
self.__configs = VConfigs()
self.__messages = Messages()
self.__embeds = VEmbeds()
async def sendNowPlaying(self, processInfo: ProcessInfo, song: Song) -> None:
# Get the lock of the playlist
playlist = processInfo.getPlaylist()
if playlist.isLoopingOne():
title = self.__messages.ONE_SONG_LOOPING
else:
title = self.__messages.SONG_PLAYING
# Create View and Embed
embed = self.__embeds.SONG_INFO(song.info, title)
view = PlayerView(self.__bot)
channel = processInfo.getTextChannel()
# Delete the previous and send the message
await self.__deletePreviousNPMessages()
await channel.send(embed=embed, view=view)
# Get the sended message
sendedMessage = await self.__getSendedMessage(channel)
# Set the message witch contains the view
view.set_message(message=sendedMessage)
self.__previousMessages.append(sendedMessage)
async def __deletePreviousNPMessages(self) -> None:
for message in self.__previousMessages:
try:
await message.delete()
except:
pass
self.__previousMessages.clear()
async def __getSendedMessage(self, channel: TextChannel) -> Message:
stringToIdentify = 'Uploader:'
last_messages: List[Message] = await channel.history(limit=5).flatten()
for message in last_messages:
try:
if message.author == self.__bot.user:
if len(message.embeds) > 0:
embed: Embed = message.embeds[0]
if len(embed.fields) > 0:
if embed.fields[0].name == stringToIdentify:
return message
except Exception as e:
print(f'DEVELOPER NOTE -> Error cleaning messages {e}')
continue

View File

@ -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)
@ -79,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

@ -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:

View File

@ -0,0 +1,79 @@
from typing import List
from discord import Button, TextChannel
from discord.ui import View
from Config.Emojis import VEmojis
from Messages.MessagesCategory import MessagesCategory
from Music.VulkanBot import VulkanBot
from Parallelism.ProcessInfo import ProcessInfo
from Config.Messages import Messages
from Music.Song import Song
from Config.Embeds import VEmbeds
from UI.Buttons.HandlerButton import HandlerButton
from UI.Views.BasicView import BasicView
from Messages.MessagesManager import MessagesManager
from Handlers.PrevHandler import PrevHandler
from Handlers.PauseHandler import PauseHandler
from Handlers.SkipHandler import SkipHandler
from Handlers.StopHandler import StopHandler
from Handlers.ResumeHandler import ResumeHandler
from Handlers.LoopHandler import LoopHandler
from Handlers.QueueHandler import QueueHandler
class ProcessCommandsExecutor:
def __init__(self, bot: VulkanBot, guildID: int) -> None:
self.__bot = bot
self.__guildID = guildID
self.__messagesManager = MessagesManager()
self.__messages = Messages()
self.__embeds = VEmbeds()
self.__emojis = VEmojis()
async def sendNowPlaying(self, processInfo: ProcessInfo, song: Song) -> None:
# Get the lock of the playlist
playlist = processInfo.getPlaylist()
if playlist.isLoopingOne():
title = self.__messages.ONE_SONG_LOOPING
else:
title = self.__messages.SONG_PLAYING
# Create View and Embed
embed = self.__embeds.SONG_INFO(song.info, title)
channel = processInfo.getTextChannel()
view = self.__getPlayerView(channel)
# Send Message and add to the MessagesManager
message = await channel.send(embed=embed, view=view)
await self.__messagesManager.addMessageAndClearPrevious(self.__guildID, MessagesCategory.NOW_PLAYING, message, view)
# Set in the view the message witch contains the view
view.set_message(message=message)
def __getPlayerView(self, channel: TextChannel) -> View:
buttons = self.__getPlayerButtons(channel)
view = BasicView(self.__bot, buttons)
return view
def __getPlayerButtons(self, textChannel: TextChannel) -> List[Button]:
"""Create the Buttons to be inserted in the Player View"""
buttons: List[Button] = []
buttons.append(HandlerButton(self.__bot, PrevHandler, self.__emojis.BACK,
textChannel, self.__guildID, MessagesCategory.PLAYER, "Back"))
buttons.append(HandlerButton(self.__bot, PauseHandler, self.__emojis.PAUSE,
textChannel, self.__guildID, MessagesCategory.PLAYER, "Pause"))
buttons.append(HandlerButton(self.__bot, ResumeHandler, self.__emojis.PLAY,
textChannel, self.__guildID, MessagesCategory.PLAYER, "Play"))
buttons.append(HandlerButton(self.__bot, StopHandler, self.__emojis.STOP,
textChannel, self.__guildID, MessagesCategory.PLAYER, "Stop"))
buttons.append(HandlerButton(self.__bot, SkipHandler, self.__emojis.SKIP,
textChannel, self.__guildID, MessagesCategory.PLAYER, "Skip"))
buttons.append(HandlerButton(self.__bot, QueueHandler, self.__emojis.QUEUE,
textChannel, self.__guildID, MessagesCategory.QUEUE, "Songs"))
buttons.append(HandlerButton(self.__bot, LoopHandler, self.__emojis.LOOP_ONE,
textChannel, self.__guildID, MessagesCategory.LOOP, "Loop One", 'One'))
buttons.append(HandlerButton(self.__bot, LoopHandler, self.__emojis.LOOP_OFF,
textChannel, self.__guildID, MessagesCategory.LOOP, "Loop Off", 'Off'))
buttons.append(HandlerButton(self.__bot, LoopHandler, self.__emojis.LOOP_ALL,
textChannel, self.__guildID, MessagesCategory.LOOP, "Loop All", 'All'))
return buttons

View File

@ -7,7 +7,7 @@ from typing import Dict, Tuple, Union
from Config.Singleton import Singleton
from discord import Guild, Interaction
from discord.ext.commands import Context
from Music.MessagesController import MessagesController
from Parallelism.ProcessExecutor import ProcessCommandsExecutor
from Music.Song import Song
from Parallelism.PlayerProcess import PlayerProcess
from Music.Playlist import Playlist
@ -30,7 +30,7 @@ class ProcessManager(Singleton):
self.__manager.start()
self.__playersProcess: Dict[Guild, ProcessInfo] = {}
self.__playersListeners: Dict[Guild, Tuple[Thread, bool]] = {}
self.__playersMessages: Dict[Guild, MessagesController] = {}
self.__playersCommandsExecutor: Dict[Guild, ProcessCommandsExecutor] = {}
def setPlayerInfo(self, guild: Guild, info: ProcessInfo):
self.__playersProcess[guild.id] = info
@ -91,7 +91,7 @@ class ProcessManager(Singleton):
thread.start()
# Create a Message Controller for this player
self.__playersMessages[guildID] = MessagesController(self.__bot)
self.__playersCommandsExecutor[guildID] = ProcessCommandsExecutor(self.__bot, guildID)
return processInfo
@ -157,7 +157,7 @@ class ProcessManager(Singleton):
def __terminateProcess(self, guildID: int) -> None:
# Delete all structures associated with the Player
del self.__playersProcess[guildID]
del self.__playersMessages[guildID]
del self.__playersCommandsExecutor[guildID]
threadListening = self.__playersListeners[guildID]
threadListening._stop()
del self.__playersListeners[guildID]
@ -174,9 +174,9 @@ class ProcessManager(Singleton):
self.__playersProcess[guildID].setStatus(ProcessStatus.SLEEPING)
async def showNowPlaying(self, guildID: int, song: Song) -> None:
messagesController = self.__playersMessages[guildID]
commandExecutor = self.__playersCommandsExecutor[guildID]
processInfo = self.__playersProcess[guildID]
await messagesController.sendNowPlaying(processInfo, song)
await commandExecutor.sendNowPlaying(processInfo, song)
class VManager(BaseManager):

View File

@ -7,7 +7,7 @@ Vulkan uses multiprocessing and asynchronous Python modules to maximize Music Pl
<p align="center">
<img src="./Assets/playermenu.jfif" />
<img src="./Assets/playermenu.jpg" />
</p>
@ -15,14 +15,20 @@ Vulkan uses multiprocessing and asynchronous Python modules to maximize Music Pl
- Play musics from Youtube, Spotify and Deezer links (Albums, Artists, Playlists and Tracks).
- Play musics in multiple discord server at the same time.
- The player contains buttons to shortcut some commands.
- Search for all musics in Queue using buttons
- Shortcut the playing of one song using dropdown menu.
- Manage the loop of one or all playing musics.
- Manage the order and remove musics from the queue.
- Shuffle the musics queue order.
<p align="center">
<img src="./Assets/vulkancommands.jpg" />
</p>
<p align="center">
<img src="./Assets/vulkancommands.jfif" />
<img src="./Assets/queuemessage.jpg" />
</p>

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,22 +0,0 @@
from discord import ButtonStyle, Interaction
from discord.ui import Button
from Config.Emojis import VEmojis
from Handlers.PrevHandler import PrevHandler
from Music.VulkanBot import VulkanBot
class BackButton(Button):
def __init__(self, bot: VulkanBot):
super().__init__(label="Back", style=ButtonStyle.secondary, emoji=VEmojis().BACK)
self.__bot = bot
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()
handler = PrevHandler(interaction, self.__bot)
response = await handler.run()
if response.embed:
await interaction.followup.send(embed=response.embed)

View File

@ -0,0 +1,48 @@
from typing import Awaitable
from Config.Emojis import VEmojis
from discord import ButtonStyle, Interaction, Message, TextChannel
from discord.ui import Button, View
from Handlers.HandlerResponse import HandlerResponse
from Messages.MessagesCategory import MessagesCategory
from Messages.MessagesManager import MessagesManager
from Music.VulkanBot import VulkanBot
class CallbackButton(Button):
"""When clicked execute an callback passing the args and kwargs"""
def __init__(self, bot: VulkanBot, cb: Awaitable, emoji: VEmojis, textChannel: TextChannel, guildID: int, category: MessagesCategory, label=None, *args, **kwargs):
super().__init__(label=label, style=ButtonStyle.secondary, emoji=emoji)
self.__channel = textChannel
self.__guildID = guildID
self.__category = category
self.__messagesManager = MessagesManager()
self.__bot = bot
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"""
# Return to Discord that this command is being processed
await interaction.response.defer()
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)
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
if message:
await self.__messagesManager.addMessageAndClearPrevious(self.__guildID, self.__category, message, response.view)
def set_view(self, view: View):
self.__view = view
def get_view(self) -> View:
return self.__view

View File

@ -0,0 +1,50 @@
from Config.Emojis import VEmojis
from discord import ButtonStyle, Interaction, Message, TextChannel
from discord.ui import Button, View
from Handlers.HandlerResponse import HandlerResponse
from Messages.MessagesCategory import MessagesCategory
from Music.VulkanBot import VulkanBot
from Handlers.AbstractHandler import AbstractHandler
from Messages.MessagesManager import MessagesManager
class HandlerButton(Button):
"""Button that will create and execute a Handler Object when clicked"""
def __init__(self, bot: VulkanBot, handler: type[AbstractHandler], emoji: VEmojis, textChannel: TextChannel, guildID: int, category: MessagesCategory, label=None, *args, **kwargs):
super().__init__(label=label, style=ButtonStyle.secondary, emoji=emoji)
self.__messagesManager = MessagesManager()
self.__category = category
self.__guildID = guildID
self.__channel = textChannel
self.__bot = bot
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"""
# Return to Discord that this command is being processed
await interaction.response.defer()
# Create the handler object
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)
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
if message:
await self.__messagesManager.addMessageAndClearPrevious(self.__guildID, self.__category, message, response.view)
def set_view(self, view: View):
self.__view = view
def get_view(self) -> View:
return self.__view

View File

@ -1,20 +0,0 @@
from discord import ButtonStyle, Interaction
from discord.ui import Button
from Config.Emojis import VEmojis
from Handlers.LoopHandler import LoopHandler
from Music.VulkanBot import VulkanBot
class LoopAllButton(Button):
def __init__(self, bot: VulkanBot):
super().__init__(label="Loop All", style=ButtonStyle.secondary, emoji=VEmojis().LOOP_ALL)
self.__bot = bot
async def callback(self, interaction: Interaction) -> None:
await interaction.response.defer()
handler = LoopHandler(interaction, self.__bot)
response = await handler.run('all')
if response.embed:
await interaction.followup.send(embed=response.embed)

View File

@ -1,20 +0,0 @@
from discord import ButtonStyle, Interaction
from discord.ui import Button
from Config.Emojis import VEmojis
from Handlers.LoopHandler import LoopHandler
from Music.VulkanBot import VulkanBot
class LoopOffButton(Button):
def __init__(self, bot: VulkanBot):
super().__init__(label="Loop Off", style=ButtonStyle.secondary, emoji=VEmojis().LOOP_OFF)
self.__bot = bot
async def callback(self, interaction: Interaction) -> None:
await interaction.response.defer()
handler = LoopHandler(interaction, self.__bot)
response = await handler.run('off')
if response.embed:
await interaction.followup.send(embed=response.embed)

View File

@ -1,20 +0,0 @@
from discord import ButtonStyle, Interaction
from discord.ui import Button
from Config.Emojis import VEmojis
from Handlers.LoopHandler import LoopHandler
from Music.VulkanBot import VulkanBot
class LoopOneButton(Button):
def __init__(self, bot: VulkanBot):
super().__init__(label="Loop One", style=ButtonStyle.secondary, emoji=VEmojis().LOOP_ONE)
self.__bot = bot
async def callback(self, interaction: Interaction) -> None:
await interaction.response.defer()
handler = LoopHandler(interaction, self.__bot)
response = await handler.run('one')
if response.embed:
await interaction.followup.send(embed=response.embed)

View File

@ -1,20 +0,0 @@
from discord import ButtonStyle, Interaction
from discord.ui import Button
from Config.Emojis import VEmojis
from Handlers.PauseHandler import PauseHandler
from Music.VulkanBot import VulkanBot
class PauseButton(Button):
def __init__(self, bot: VulkanBot):
super().__init__(label="Pause", style=ButtonStyle.secondary, emoji=VEmojis().PAUSE)
self.__bot = bot
async def callback(self, interaction: Interaction) -> None:
await interaction.response.defer()
handler = PauseHandler(interaction, self.__bot)
response = await handler.run()
if response.embed:
await interaction.followup.send(embed=response.embed)

View File

@ -1,20 +0,0 @@
from discord import ButtonStyle, Interaction
from discord.ui import Button
from Config.Emojis import VEmojis
from Music.VulkanBot import VulkanBot
from Handlers.ResumeHandler import ResumeHandler
class PlayButton(Button):
def __init__(self, bot: VulkanBot):
super().__init__(label="Play", style=ButtonStyle.secondary, emoji=VEmojis().PLAY)
self.__bot = bot
async def callback(self, interaction: Interaction) -> None:
await interaction.response.defer()
handler = ResumeHandler(interaction, self.__bot)
response = await handler.run()
if response.embed:
await interaction.followup.send(embed=response.embed)

View File

@ -0,0 +1,88 @@
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, the pycord library doesn't accept more
songsNames: List[str] = []
for x in range(20):
songsNames.append(f'{x + 1} - {songs[x].title[:80]}')
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 outdated",
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, response.view)
# 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

View File

@ -1,20 +0,0 @@
from discord import ButtonStyle, Interaction
from discord.ui import Button
from Config.Emojis import VEmojis
from Music.VulkanBot import VulkanBot
from Handlers.SkipHandler import SkipHandler
class SkipButton(Button):
def __init__(self, bot: VulkanBot):
super().__init__(label="Skip", style=ButtonStyle.secondary, emoji=VEmojis().SKIP)
self.__bot = bot
async def callback(self, interaction: Interaction) -> None:
await interaction.response.defer()
handler = SkipHandler(interaction, self.__bot)
response = await handler.run()
if response.embed:
await interaction.followup.send(embed=response.embed)

View File

@ -1,20 +0,0 @@
from Handlers.QueueHandler import QueueHandler
from discord import ButtonStyle, Interaction
from discord.ui import Button
from Config.Emojis import VEmojis
from Music.VulkanBot import VulkanBot
class SongsButton(Button):
def __init__(self, bot: VulkanBot):
super().__init__(label="Songs", style=ButtonStyle.secondary, emoji=VEmojis().QUEUE)
self.__bot = bot
async def callback(self, interaction: Interaction) -> None:
await interaction.response.defer()
handler = QueueHandler(interaction, self.__bot)
response = await handler.run()
if response.embed:
await interaction.followup.send(embed=response.embed)

View File

@ -1,20 +0,0 @@
from discord import ButtonStyle, Interaction
from discord.ui import Button
from Config.Emojis import VEmojis
from Music.VulkanBot import VulkanBot
from Handlers.StopHandler import StopHandler
class StopButton(Button):
def __init__(self, bot: VulkanBot):
super().__init__(label="Stop", style=ButtonStyle.secondary, emoji=VEmojis().STOP)
self.__bot = bot
async def callback(self, interaction: Interaction) -> None:
await interaction.response.defer()
handler = StopHandler(interaction, self.__bot)
response = await handler.run()
if response.embed:
await interaction.followup.send(embed=response.embed)

View File

@ -1,11 +0,0 @@
from UI.Responses.AbstractCogResponse import AbstractCommandResponse
from Handlers.HandlerResponse import HandlerResponse
class EmbedCommandResponse(AbstractCommandResponse):
def __init__(self, response: HandlerResponse) -> None:
super().__init__(response)
async def run(self) -> None:
if self.response.embed:
await self.context.send(embed=self.response.embed)

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

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

52
UI/Views/BasicView.py Normal file
View File

@ -0,0 +1,52 @@
from typing import List
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, AbstractView):
"""View that receives buttons to hold, in timeout disable buttons"""
def __init__(self, bot: VulkanBot, buttons: List[AbstractItem], timeout: float = 6000):
super().__init__(timeout=timeout)
self.__bot = bot
self.__message: Message = None
self.__working = True
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)
def stopView(self):
self.__working = False
async def on_timeout(self) -> None:
# Disable all itens and, if has the message, edit it
try:
if not self.__working:
return
self.disable_all_items()
if self.__message is not None and isinstance(self.__message, Message):
await self.__message.edit(f"{emojis.MUSIC} - The buttons in this message have been disabled due timeout", view=self)
except Exception as e:
print(f'[ERROR EDITING MESSAGE] -> {e}')
def set_message(self, message: Message) -> None:
self.__message = message
async def update(self):
try:
if not self.__working:
return
if self.__message is not None:
await self.__message.edit(view=self)
except Exception as e:
print(f'[ERROR UPDATING MESSAGE] -> {e}')

View File

@ -1,43 +0,0 @@
from discord import Message
from discord.ui import View
from Config.Emojis import VEmojis
from UI.Buttons.PauseButton import PauseButton
from UI.Buttons.BackButton import BackButton
from UI.Buttons.SkipButton import SkipButton
from UI.Buttons.StopButton import StopButton
from UI.Buttons.SongsButton import SongsButton
from UI.Buttons.PlayButton import PlayButton
from UI.Buttons.LoopAllButton import LoopAllButton
from UI.Buttons.LoopOneButton import LoopOneButton
from UI.Buttons.LoopOffButton import LoopOffButton
from Music.VulkanBot import VulkanBot
emojis = VEmojis()
class PlayerView(View):
def __init__(self, bot: VulkanBot, timeout: float = 6000):
super().__init__(timeout=timeout)
self.__bot = bot
self.__message: Message = None
self.add_item(BackButton(self.__bot))
self.add_item(PauseButton(self.__bot))
self.add_item(PlayButton(self.__bot))
self.add_item(StopButton(self.__bot))
self.add_item(SkipButton(self.__bot))
self.add_item(SongsButton(self.__bot))
self.add_item(LoopOneButton(self.__bot))
self.add_item(LoopOffButton(self.__bot))
self.add_item(LoopAllButton(self.__bot))
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