From 3b198cf78a378e0101360b94a2933958bf04cba5 Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Thu, 22 Sep 2022 16:20:05 -0300 Subject: [PATCH] Adding support to slash commands --- Config/Embeds.py | 5 + Config/Helper.py | 9 +- Config/Messages.py | 1 + DiscordCogs/MusicCog.py | 7 +- DiscordCogs/SlashCog.py | 271 +++++++++++++++++++++++ Handlers/ClearHandler.py | 3 +- Handlers/PlayHandler.py | 3 +- Handlers/QueueHandler.py | 10 +- Handlers/RemoveHandler.py | 7 +- Messages/DiscordMessages.py | 39 ++++ Messages/MessagesManager.py | 18 +- Messages/Responses/EmbedCogResponse.py | 9 +- Messages/Responses/EmoteCogResponse.py | 4 + Messages/Responses/SlashEmbedResponse.py | 35 +++ Music/VulkanBot.py | 7 +- Music/VulkanInitializer.py | 15 +- README.md | 3 +- UI/Views/BasicView.py | 16 +- 18 files changed, 422 insertions(+), 40 deletions(-) create mode 100644 DiscordCogs/SlashCog.py create mode 100644 Messages/DiscordMessages.py create mode 100644 Messages/Responses/SlashEmbedResponse.py diff --git a/Config/Embeds.py b/Config/Embeds.py index 3de3466..6e298a6 100644 --- a/Config/Embeds.py +++ b/Config/Embeds.py @@ -396,6 +396,11 @@ def PLAYLIST_RANGE_ERROR(self) -> Embed: ) return embed + def PLAYLIST_CLEAR(self) -> Embed: + return Embed( + description=self.__messages.PLAYLIST_CLEAR + ) + def CARA_COROA(self, result: str) -> Embed: embed = Embed( title='Cara Coroa', diff --git a/Config/Helper.py b/Config/Helper.py index 49ee40c..22c611c 100644 --- a/Config/Helper.py +++ b/Config/Helper.py @@ -21,8 +21,8 @@ def __init__(self) -> None: Off - Disable loop.""" self.HELP_NP = 'Show the info of the current song.' self.HELP_NP_LONG = 'Show the information of the song being played.\n\nRequire: A song being played.\nArguments: None.' - self.HELP_QUEUE = f'Show the first {config.MAX_PRELOAD_SONGS} songs in queue.' - self.HELP_QUEUE_LONG = f'Show the first {config.MAX_PRELOAD_SONGS} song in the queue.\n\nArguments: None.' + self.HELP_QUEUE = f'Show the first {config.MAX_SONGS_IN_PAGE} songs in queue.' + self.HELP_QUEUE_LONG = f'Show the first {config.MAX_SONGS_IN_PAGE} song in the queue.\n\nArguments: None.' self.HELP_PAUSE = 'Pauses the song player.' self.HELP_PAUSE_LONG = 'If playing, pauses the song player.\n\nArguments: None' self.HELP_PREV = 'Play the previous song.' @@ -33,7 +33,7 @@ def __init__(self) -> None: self.HELP_PLAY_LONG = 'Play a song in discord. \n\nRequire: You to be connected to a voice channel.\nArguments: Youtube, Spotify or Deezer song/playlist link or the title of the song to be searched in Youtube.' self.HELP_HISTORY = f'Show the history of played songs.' self.HELP_HISTORY_LONG = f'Show the last {config.MAX_SONGS_HISTORY} played songs' - self.HELP_MOVE = 'Moves a song from position x to y in queue.' + self.HELP_MOVE = 'Moves a song from position pos1 to pos2 in queue.' self.HELP_MOVE_LONG = 'Moves a song from position x to position y in queue.\n\nRequire: Positions to be both valid numbers.\nArguments: 1º Number => Initial position, 2º Number => Destination position. Both numbers could be -1 to refer to the last song in queue.\nDefault: By default, if the second number is not passed, it will be 1, moving the selected song to 1º position.' self.HELP_REMOVE = 'Remove a song in position x.' self.HELP_REMOVE_LONG = 'Remove a song from queue in the position passed.\n\nRequire: Position to be a valid number.\nArguments: 1º self.Number => Position in queue of the song.' @@ -49,3 +49,6 @@ def __init__(self) -> None: self.HELP_CHOOSE_LONG = 'Choose randomly one item passed in this command.\n\nRequire: Itens to be separated by comma.\nArguments: As much as you want.' self.HELP_CARA = 'Return cara or coroa.' self.HELP_CARA_LONG = 'Return cara or coroa.' + + self.SLASH_QUEUE_DESCRIPTION = f'Number of queue page, there is only {config.MAX_SONGS_IN_PAGE} musics by page' + self.SLASH_MOVE_HELP = 'Moves a song from position pos1 to pos2 in queue.' diff --git a/Config/Messages.py b/Config/Messages.py index b243ce5..00f5ea0 100644 --- a/Config/Messages.py +++ b/Config/Messages.py @@ -31,6 +31,7 @@ def __init__(self) -> None: 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...' + self.PLAYLIST_CLEAR = f'{self.__emojis.MUSIC} Playlist is now empty' self.HISTORY_TITLE = f'{self.__emojis.MUSIC} Played Songs' self.HISTORY_EMPTY = f'{self.__emojis.QUEUE} There is no musics in history' diff --git a/DiscordCogs/MusicCog.py b/DiscordCogs/MusicCog.py index b5e6252..0e78b72 100644 --- a/DiscordCogs/MusicCog.py +++ b/DiscordCogs/MusicCog.py @@ -45,7 +45,12 @@ async def play(self, ctx: Context, *args) -> None: try: controller = PlayHandler(ctx, self.__bot) - response = await controller.run(args) + if len(args) > 1: + track = " ".join(args) + else: + track = args + + response = await controller.run(track) if response is not None: cogResponser1 = EmbedCommandResponse(response, MessagesCategory.PLAYER) cogResponser2 = EmoteCommandResponse(response, MessagesCategory.PLAYER) diff --git a/DiscordCogs/SlashCog.py b/DiscordCogs/SlashCog.py new file mode 100644 index 0000000..5ee2a19 --- /dev/null +++ b/DiscordCogs/SlashCog.py @@ -0,0 +1,271 @@ +from discord.ext.commands import slash_command, Cog +from discord import Option, ApplicationContext, OptionChoice +from Handlers.ClearHandler import ClearHandler +from Handlers.MoveHandler import MoveHandler +from Handlers.NowPlayingHandler import NowPlayingHandler +from Handlers.PlayHandler import PlayHandler +from Handlers.PrevHandler import PrevHandler +from Handlers.RemoveHandler import RemoveHandler +from Handlers.ResetHandler import ResetHandler +from Handlers.ShuffleHandler import ShuffleHandler +from Handlers.SkipHandler import SkipHandler +from Handlers.PauseHandler import PauseHandler +from Handlers.StopHandler import StopHandler +from Handlers.ResumeHandler import ResumeHandler +from Handlers.HistoryHandler import HistoryHandler +from Handlers.QueueHandler import QueueHandler +from Handlers.LoopHandler import LoopHandler +from Messages.MessagesCategory import MessagesCategory +from Messages.Responses.SlashEmbedResponse import SlashEmbedResponse +from Music.VulkanBot import VulkanBot +from Config.Embeds import VEmbeds +from Config.Helper import Helper +import traceback + +helper = Helper() + + +class SlashCommands(Cog): + """ + Class to listen to Music commands + It'll listen for commands from discord, when triggered will create a specific Handler for the command + Execute the handler and then create a specific View to be showed in Discord + """ + + def __init__(self, bot: VulkanBot) -> None: + self.__bot: VulkanBot = bot + self.__embeds = VEmbeds() + + @slash_command(name="play", description=helper.HELP_PLAY) + async def play(self, ctx: ApplicationContext, + music: Option(str, "The music name or URL", required=True)) -> None: + # Due to the utilization of multiprocessing module in this Project, we have multiple instances of the Bot, and by using this flag + # we can control witch bot instance will listen to the commands that Discord send to our application + if not self.__bot.listingSlash: + return + try: + await ctx.defer() + controller = PlayHandler(ctx, self.__bot) + + response = await controller.run(music) + if response is not None: + cogResponser1 = SlashEmbedResponse(response, ctx, MessagesCategory.PLAYER) + await cogResponser1.run() + except Exception: + print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}') + + @slash_command(name="queue", description=helper.HELP_QUEUE) + async def queue(self, ctx: ApplicationContext, + page_number: Option(int, helper.SLASH_QUEUE_DESCRIPTION, min_value=1, default=1)) -> None: + if not self.__bot.listingSlash: + return + try: + await ctx.defer() + controller = QueueHandler(ctx, self.__bot) + + # Change index 1 to 0 + page_number -= 1 + response = await controller.run(page_number) + + cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.QUEUE) + await cogResponser.run() + except Exception: + print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}') + + @slash_command(name="skip", description=helper.HELP_SKIP) + async def skip(self, ctx: ApplicationContext) -> None: + if not self.__bot.listingSlash: + return + try: + await ctx.defer() + controller = SkipHandler(ctx, self.__bot) + + response = await controller.run() + cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.PLAYER) + await cogResponser.run() + except Exception: + print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}') + + @slash_command(name='stop', description=helper.HELP_STOP) + async def stop(self, ctx: ApplicationContext) -> None: + if not self.__bot.listingSlash: + return + try: + await ctx.defer() + controller = StopHandler(ctx, self.__bot) + + response = await controller.run() + cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.PLAYER) + await cogResponser.run() + except Exception: + print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}') + + @slash_command(name='pause', description=helper.HELP_PAUSE) + async def pause(self, ctx: ApplicationContext) -> None: + if not self.__bot.listingSlash: + return + try: + await ctx.defer() + controller = PauseHandler(ctx, self.__bot) + + response = await controller.run() + cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.PLAYER) + await cogResponser.run() + except Exception: + print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}') + + @slash_command(name='resume', description=helper.HELP_RESUME) + async def resume(self, ctx: ApplicationContext) -> None: + if not self.__bot.listingSlash: + return + try: + await ctx.defer() + controller = ResumeHandler(ctx, self.__bot) + + response = await controller.run() + cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.PLAYER) + await cogResponser.run() + except Exception: + print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}') + + @slash_command(name='previous', description=helper.HELP_PREV) + async def previous(self, ctx: ApplicationContext) -> None: + if not self.__bot.listingSlash: + return + try: + await ctx.defer() + controller = PrevHandler(ctx, self.__bot) + + response = await controller.run() + if response is not None: + cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.PLAYER) + await cogResponser.run() + except Exception: + print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}') + + @slash_command(name='history', description=helper.HELP_HISTORY) + async def history(self, ctx: ApplicationContext) -> None: + if not self.__bot.listingSlash: + return + try: + await ctx.defer() + controller = HistoryHandler(ctx, self.__bot) + + response = await controller.run() + cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.HISTORY) + await cogResponser.run() + except Exception: + print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}') + + @slash_command(name='loop', description=helper.HELP_LOOP) + async def loop(self, ctx: ApplicationContext, + loop_type: Option(str, choices=[ + OptionChoice(name='off', value='off'), + OptionChoice(name='one', value='one'), + OptionChoice(name='all', value='all') + ])) -> None: + if not self.__bot.listingSlash: + return + try: + await ctx.defer() + controller = LoopHandler(ctx, self.__bot) + + response = await controller.run(loop_type) + cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.LOOP) + await cogResponser.run() + except Exception: + print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}') + + @slash_command(name='clear', description=helper.HELP_CLEAR) + async def clear(self, ctx: ApplicationContext) -> None: + if not self.__bot.listingSlash: + return + try: + await ctx.defer() + controller = ClearHandler(ctx, self.__bot) + + response = await controller.run() + cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.PLAYER) + await cogResponser.run() + except Exception: + print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}') + + @slash_command(name='now_playing', description=helper.HELP_NP) + async def now_playing(self, ctx: ApplicationContext) -> None: + if not self.__bot.listingSlash: + return + try: + await ctx.defer() + controller = NowPlayingHandler(ctx, self.__bot) + + response = await controller.run() + cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.NOW_PLAYING) + await cogResponser.run() + except Exception: + print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}') + + @slash_command(name='shuffle_songs', description=helper.HELP_SHUFFLE) + async def shuffle(self, ctx: ApplicationContext) -> None: + if not self.__bot.listingSlash: + return + try: + await ctx.defer() + controller = ShuffleHandler(ctx, self.__bot) + + response = await controller.run() + cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.PLAYER) + await cogResponser.run() + except Exception: + print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}') + + @slash_command(name='move_song', description=helper.SLASH_MOVE_HELP) + async def move(self, ctx: ApplicationContext, + from_pos: Option(int, "The position of song to move", min_value=1), + to_pos: Option(int, "The position to put the song, default 1", min_value=1, default=1)) -> None: + if not self.__bot.listingSlash: + return + try: + await ctx.defer() + if from_pos == 0: + from_pos = 1 + + controller = MoveHandler(ctx, self.__bot) + + response = await controller.run(from_pos, to_pos) + cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.MANAGING_QUEUE) + await cogResponser.run() + except Exception: + print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}') + + @slash_command(name='remove', description=helper.HELP_REMOVE) + async def remove(self, ctx: ApplicationContext, + position: Option(int, "The song position to remove", min_value=1)) -> None: + if not self.__bot.listingSlash: + return + try: + await ctx.defer() + controller = RemoveHandler(ctx, self.__bot) + + response = await controller.run(position) + cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.MANAGING_QUEUE) + await cogResponser.run() + except Exception: + print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}') + + @slash_command(name='reset', description=helper.HELP_RESET) + async def reset(self, ctx: ApplicationContext) -> None: + if not self.__bot.listingSlash: + return + try: + await ctx.defer() + controller = ResetHandler(ctx, self.__bot) + + response = await controller.run() + cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.PLAYER) + await cogResponser.run() + except Exception: + print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}') + + +def setup(bot): + bot.add_cog(SlashCommands(bot)) diff --git a/Handlers/ClearHandler.py b/Handlers/ClearHandler.py index 978cf32..216e030 100644 --- a/Handlers/ClearHandler.py +++ b/Handlers/ClearHandler.py @@ -23,7 +23,8 @@ async def run(self) -> HandlerResponse: if acquired: playlist.clear() processLock.release() - return HandlerResponse(self.ctx) + embed = self.embeds.PLAYLIST_CLEAR() + return HandlerResponse(self.ctx, embed) else: processManager.resetProcess(self.guild, self.ctx) embed = self.embeds.PLAYER_RESTARTED() diff --git a/Handlers/PlayHandler.py b/Handlers/PlayHandler.py index 505e793..0f5b6a4 100644 --- a/Handlers/PlayHandler.py +++ b/Handlers/PlayHandler.py @@ -21,8 +21,7 @@ def __init__(self, ctx: Union[Context, Interaction], bot: VulkanBot) -> None: self.__searcher = Searcher() self.__down = Downloader() - async def run(self, args: str) -> HandlerResponse: - track = " ".join(args) + async def run(self, track: str) -> HandlerResponse: requester = self.ctx.author.name if not self.__isUserConnected(): diff --git a/Handlers/QueueHandler.py b/Handlers/QueueHandler.py index f936f0b..180402b 100644 --- a/Handlers/QueueHandler.py +++ b/Handlers/QueueHandler.py @@ -47,11 +47,11 @@ async def run(self, pageNumber=0) -> HandlerResponse: 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) + # Truncate the pageNumber to the closest value + if pageNumber < 0: + pageNumber = 0 + elif pageNumber >= len(songsPages): + pageNumber = len(songsPages) - 1 # Select the page in queue to be printed songs = songsPages[pageNumber] diff --git a/Handlers/RemoveHandler.py b/Handlers/RemoveHandler.py index b55d61a..0d032cd 100644 --- a/Handlers/RemoveHandler.py +++ b/Handlers/RemoveHandler.py @@ -1,10 +1,10 @@ -from typing import Union from discord.ext.commands import Context from Handlers.AbstractHandler import AbstractHandler from Handlers.HandlerResponse import HandlerResponse from Config.Exceptions import BadCommandUsage, VulkanError, ErrorRemoving, InvalidInput, NumberRequired from Music.Playlist import Playlist from Music.VulkanBot import VulkanBot +from Parallelism.ProcessInfo import ProcessInfo from typing import Union from discord import Interaction @@ -16,15 +16,14 @@ def __init__(self, ctx: Union[Context, Interaction], bot: VulkanBot) -> None: async def run(self, position: str) -> HandlerResponse: # Get the current process of the guild processManager = self.config.getProcessManager() - processInfo = processManager.getRunningPlayerInfo(self.guild) + processInfo: ProcessInfo = processManager.getRunningPlayerInfo(self.guild) if not processInfo: - # Clear the playlist embed = self.embeds.NOT_PLAYING() error = BadCommandUsage() return HandlerResponse(self.ctx, embed, error) playlist = processInfo.getPlaylist() - if playlist.getCurrentSong() is None: + if playlist is None: embed = self.embeds.NOT_PLAYING() error = BadCommandUsage() return HandlerResponse(self.ctx, embed, error) diff --git a/Messages/DiscordMessages.py b/Messages/DiscordMessages.py new file mode 100644 index 0000000..bf76d40 --- /dev/null +++ b/Messages/DiscordMessages.py @@ -0,0 +1,39 @@ +from discord import Message, WebhookMessage +from abc import ABC, abstractmethod + + +class VAbstractMessage(ABC): + """ + Abstract class to allow create a pattern when dealing with multiple Discord + messages types, such as Interaction Messages and the standard discord messages + that contains two different ways of deletion + """ + @abstractmethod + async def delete(self): + pass + + +class VWebHookMessage(VAbstractMessage): + """ + Holds a WebhookMessage instance + """ + + def __init__(self, message: WebhookMessage) -> None: + self.__message = message + super().__init__() + + async def delete(self): + await self.__message.delete() + + +class VDefaultMessage(VAbstractMessage): + """ + Holds a Message instance, the basic Discord message type + """ + + def __init__(self, message: Message) -> None: + self.__message = message + super().__init__() + + async def delete(self): + await self.__message.delete() diff --git a/Messages/MessagesManager.py b/Messages/MessagesManager.py index 58140b0..041b617 100644 --- a/Messages/MessagesManager.py +++ b/Messages/MessagesManager.py @@ -1,19 +1,20 @@ 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 +from Messages.DiscordMessages import VAbstractMessage +import traceback 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]]] = {} + self.__guildsMessages: Dict[int, Dict[MessagesCategory, List[VAbstractMessage]]] = {} # Will, for each message, store the AbstractView that controls it - self.__messagesViews: Dict[Message, AbstractView] = {} + self.__messagesViews: Dict[VAbstractMessage, AbstractView] = {} - def addMessage(self, guildID: int, category: MessagesCategory, message: Message, view: AbstractView = None) -> None: + def addMessage(self, guildID: int, category: MessagesCategory, message: VAbstractMessage, view: AbstractView = None) -> None: if message is None: return @@ -29,7 +30,7 @@ def addMessage(self, guildID: int, category: MessagesCategory, message: Message, self.__messagesViews[message] = view sendedMessages.append(message) - async def addMessageAndClearPrevious(self, guildID: int, category: MessagesCategory, message: Message, view: AbstractView = None) -> None: + async def addMessageAndClearPrevious(self, guildID: int, category: MessagesCategory, message: VAbstractMessage, view: AbstractView = None) -> None: if message is None: return @@ -66,7 +67,7 @@ async def clearMessagesOfGuild(self, guildID: int) -> None: for message in categoriesMessages[category]: self.__deleteMessage(message) - async def __deleteMessage(self, message: Message) -> None: + async def __deleteMessage(self, message: VAbstractMessage) -> None: try: # If there is a view for this message delete the key if message in self.__messagesViews.keys(): @@ -75,6 +76,5 @@ async def __deleteMessage(self, message: Message) -> None: del messageView await message.delete() - except Exception as e: - print(f'[ERROR DELETING MESSAGE] -> {e}') - pass + except Exception: + print(f'[ERROR DELETING MESSAGE] -> {traceback.format_exc()}') diff --git a/Messages/Responses/EmbedCogResponse.py b/Messages/Responses/EmbedCogResponse.py index 058d1d0..3960cba 100644 --- a/Messages/Responses/EmbedCogResponse.py +++ b/Messages/Responses/EmbedCogResponse.py @@ -1,6 +1,7 @@ from Messages.Responses.AbstractCogResponse import AbstractCommandResponse from Handlers.HandlerResponse import HandlerResponse from Messages.MessagesCategory import MessagesCategory +from Messages.DiscordMessages import VAbstractMessage, VDefaultMessage class EmbedCommandResponse(AbstractCommandResponse): @@ -9,16 +10,20 @@ def __init__(self, response: HandlerResponse, category: MessagesCategory) -> Non async def run(self, deleteLast: bool = True) -> None: message = None + # If the response has both embed and view to be sended 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) + + # Or just a embed elif self.response.embed: message = await self.context.send(embed=self.response.embed) if message: + vMessage: VAbstractMessage = VDefaultMessage(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) + await self.manager.addMessageAndClearPrevious(self.context.guild.id, self.category, vMessage, self.response.view) else: - self.manager.addMessage(self.context.guild.id, self.category, message) + self.manager.addMessage(self.context.guild.id, self.category, vMessage) diff --git a/Messages/Responses/EmoteCogResponse.py b/Messages/Responses/EmoteCogResponse.py index c1d62bc..66eb683 100644 --- a/Messages/Responses/EmoteCogResponse.py +++ b/Messages/Responses/EmoteCogResponse.py @@ -11,6 +11,10 @@ def __init__(self, response: HandlerResponse, category: MessagesCategory) -> Non self.__emojis = VEmojis() async def run(self, deleteLast: bool = True) -> None: + # Now with Discord Interactions some commands are triggered without message + if (self.message is None): + return None + if self.response.success: await self.message.add_reaction(self.__emojis.SUCCESS) else: diff --git a/Messages/Responses/SlashEmbedResponse.py b/Messages/Responses/SlashEmbedResponse.py new file mode 100644 index 0000000..f0017b6 --- /dev/null +++ b/Messages/Responses/SlashEmbedResponse.py @@ -0,0 +1,35 @@ +from Messages.Responses.AbstractCogResponse import AbstractCommandResponse +from Handlers.HandlerResponse import HandlerResponse +from Messages.MessagesCategory import MessagesCategory +from Messages.DiscordMessages import VAbstractMessage, VWebHookMessage +from discord import ApplicationContext + + +class SlashEmbedResponse(AbstractCommandResponse): + def __init__(self, response: HandlerResponse, ctx: ApplicationContext, category: MessagesCategory) -> None: + self.__ctx = ctx + super().__init__(response, category) + + async def run(self, deleteLast: bool = True) -> None: + message = None + # If the response has both embed and view to send + if self.response.embed and self.response.view: + # Respond the Slash command and set the view to contain the sended message + message = await self.__ctx.send_followup(embed=self.response.embed, view=self.response.view) + self.response.view.set_message(message) + + # If the response only has the embed then send the embed + elif self.response.embed: + message = await self.__ctx.send_followup(embed=self.response.embed) + else: + message = await self.__ctx.send_followup('Ok!') + + # If any message was sended + if message: + # Convert the Discord message type to an Vulkan type + vMessage: VAbstractMessage = VWebHookMessage(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, vMessage, self.response.view) + else: + self.manager.addMessage(self.context.guild.id, self.category, vMessage) diff --git a/Music/VulkanBot.py b/Music/VulkanBot.py index 8a8ba2a..ed619b8 100644 --- a/Music/VulkanBot.py +++ b/Music/VulkanBot.py @@ -8,13 +8,18 @@ class VulkanBot(Bot): - def __init__(self, *args, **kwargs): + def __init__(self, listingSlash: bool = False, *args, **kwargs): super().__init__(*args, **kwargs) + self.__listingSlash = listingSlash self.__configs = VConfigs() self.__messages = Messages() self.__embeds = VEmbeds() self.remove_command("help") + @property + def listingSlash(self) -> bool: + return self.__listingSlash + def startBot(self) -> None: """Blocking function that will start the bot""" if self.__configs.BOT_TOKEN == '': diff --git a/Music/VulkanInitializer.py b/Music/VulkanInitializer.py index 08ca1a0..c842cea 100644 --- a/Music/VulkanInitializer.py +++ b/Music/VulkanInitializer.py @@ -23,13 +23,18 @@ def getBot(self) -> VulkanBot: def __create_bot(self, willListen: bool) -> VulkanBot: if willListen: prefix = self.__config.BOT_PREFIX + bot = VulkanBot(listingSlash=True, + command_prefix=prefix, + pm_help=True, + case_insensitive=True, + intents=self.__intents) else: prefix = ''.join(choices(string.ascii_uppercase + string.digits, k=4)) - - bot = VulkanBot(command_prefix=prefix, - pm_help=True, - case_insensitive=True, - intents=self.__intents) + bot = VulkanBot(listingSlash=False, + command_prefix=prefix, + pm_help=True, + case_insensitive=True, + intents=self.__intents) return bot def __add_cogs(self, bot: Bot) -> None: diff --git a/README.md b/README.md index 135dad6..4fbf3cf 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,8 @@ 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 +- Support for the new Discord Slash 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. diff --git a/UI/Views/BasicView.py b/UI/Views/BasicView.py index 46f04ed..7dcfa8a 100644 --- a/UI/Views/BasicView.py +++ b/UI/Views/BasicView.py @@ -1,16 +1,19 @@ -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 +from Music.VulkanBot import VulkanBot +from Config.Emojis import VEmojis +from discord import Message +from discord.ui import View +from typing import List emojis = VEmojis() class BasicView(View, AbstractView): - """View that receives buttons to hold, in timeout disable buttons""" + """ + View class that inherits from the Discord View Class, managing a list of Buttons + and the message that holds this View. + """ def __init__(self, bot: VulkanBot, buttons: List[AbstractItem], timeout: float = 6000): super().__init__(timeout=timeout) @@ -42,6 +45,7 @@ def set_message(self, message: Message) -> None: self.__message = message async def update(self): + """Edit the message sending the view again""" try: if not self.__working: return