Adapting History, Loop, Now Playing, Remove, Shuffle and Reset commands to work with process

This commit is contained in:
Rafael Vargas
2022-07-23 13:57:47 -03:00
parent 3eab6176c3
commit 7efed8ab89
13 changed files with 141 additions and 69 deletions

View File

@@ -16,7 +16,7 @@ class Configs(Singleton):
'[ERROR] -> You must create and .env file with all required fields, see documentation for help') '[ERROR] -> You must create and .env file with all required fields, see documentation for help')
self.CLEANER_MESSAGES_QUANT = 5 self.CLEANER_MESSAGES_QUANT = 5
self.COMMANDS_PATH = 'Commands' self.COMMANDS_PATH = 'DiscordCogs'
self.VC_TIMEOUT = 600 self.VC_TIMEOUT = 600
self.MAX_PLAYLIST_LENGTH = 50 self.MAX_PLAYLIST_LENGTH = 50

View File

@@ -42,6 +42,8 @@ class Messages(Singleton):
self.LOOP_DISABLE = '➡️ Loop disabled' self.LOOP_DISABLE = '➡️ Loop disabled'
self.LOOP_ALREADY_DISABLE = '❌ Loop is already disabled' self.LOOP_ALREADY_DISABLE = '❌ Loop is already disabled'
self.LOOP_ON = f'❌ This command cannot be invoked with any loop activated. Use {configs.BOT_PREFIX}loop off to disable loop' self.LOOP_ON = f'❌ This command cannot be invoked with any loop activated. Use {configs.BOT_PREFIX}loop off to disable loop'
self.BAD_USE_OF_LOOP = f"""❌ Invalid arguments of Loop command. Use {configs.BOT_PREFIX}help loop to more information.
-> Available Arguments: ["all", "off", "one", ""]"""
self.SONGS_SHUFFLED = '🔀 Songs shuffled successfully' self.SONGS_SHUFFLED = '🔀 Songs shuffled successfully'
self.ERROR_SHUFFLING = '❌ Error while shuffling the songs' self.ERROR_SHUFFLING = '❌ Error while shuffling the songs'

View File

@@ -21,7 +21,6 @@ from Handlers.QueueHandler import QueueHandler
from Handlers.LoopHandler import LoopHandler from Handlers.LoopHandler import LoopHandler
from Views.EmoteView import EmoteView from Views.EmoteView import EmoteView
from Views.EmbedView import EmbedView from Views.EmbedView import EmbedView
from Parallelism.ProcessManager import ProcessManager
helper = Helper() helper = Helper()
@@ -120,7 +119,7 @@ class MusicCog(commands.Cog):
await view1.run() await view1.run()
await view2.run() await view2.run()
@commands.command(name='history', help=helper.HELP_HISTORY, description=helper.HELP_HISTORY_LONG, aliases=['historico', 'h']) @commands.command(name='history', help=helper.HELP_HISTORY, description=helper.HELP_HISTORY_LONG, aliases=['historico', 'anteriores', 'hist'])
async def history(self, ctx: Context) -> None: async def history(self, ctx: Context) -> None:
controller = HistoryHandler(ctx, self.__bot) controller = HistoryHandler(ctx, self.__bot)
@@ -158,7 +157,7 @@ class MusicCog(commands.Cog):
await view1.run() await view1.run()
await view2.run() await view2.run()
@commands.command(name='shuffle', help=helper.HELP_SHUFFLE, description=helper.HELP_SHUFFLE_LONG, aliases=['aleatorio']) @commands.command(name='shuffle', help=helper.HELP_SHUFFLE, description=helper.HELP_SHUFFLE_LONG, aliases=['aleatorio', 'misturar'])
async def shuffle(self, ctx: Context) -> None: async def shuffle(self, ctx: Context) -> None:
controller = ShuffleHandler(ctx, self.__bot) controller = ShuffleHandler(ctx, self.__bot)

View File

@@ -13,7 +13,7 @@ class RandomCog(Cog):
def __init__(self, bot: Client): def __init__(self, bot: Client):
self.__embeds = Embeds() self.__embeds = Embeds()
@command(name='random', help=helper.HELP_RANDOM, description=helper.HELP_RANDOM_LONG, aliases=['rand', 'aleatorio']) @command(name='random', help=helper.HELP_RANDOM, description=helper.HELP_RANDOM_LONG, aliases=['rand'])
async def random(self, ctx: Context, arg: str) -> None: async def random(self, ctx: Context, arg: str) -> None:
try: try:
arg = int(arg) arg = int(arg)

View File

@@ -3,6 +3,7 @@ from discord import Client
from Handlers.AbstractHandler import AbstractHandler from Handlers.AbstractHandler import AbstractHandler
from Handlers.HandlerResponse import HandlerResponse from Handlers.HandlerResponse import HandlerResponse
from Utils.Utils import Utils from Utils.Utils import Utils
from Parallelism.ProcessManager import ProcessManager
class HistoryHandler(AbstractHandler): class HistoryHandler(AbstractHandler):
@@ -10,11 +11,18 @@ class HistoryHandler(AbstractHandler):
super().__init__(ctx, bot) super().__init__(ctx, bot)
async def run(self) -> HandlerResponse: async def run(self) -> HandlerResponse:
history = self.player.playlist.getSongsHistory() # Get the current process of the guild
processManager = ProcessManager()
processContext = processManager.getRunningPlayerContext(self.guild)
if processContext:
with processContext.getLock():
playlist = processContext.getPlaylist()
history = playlist.getSongsHistory()
else:
history = []
if len(history) == 0: if len(history) == 0:
text = self.messages.HISTORY_EMPTY text = self.messages.HISTORY_EMPTY
else: else:
text = f'\n📜 History Length: {len(history)} | Max: {self.config.MAX_SONGS_HISTORY}\n' text = f'\n📜 History Length: {len(history)} | Max: {self.config.MAX_SONGS_HISTORY}\n'
for pos, song in enumerate(history, start=1): for pos, song in enumerate(history, start=1):

View File

@@ -3,6 +3,7 @@ from discord import Client
from Handlers.AbstractHandler import AbstractHandler from Handlers.AbstractHandler import AbstractHandler
from Handlers.HandlerResponse import HandlerResponse from Handlers.HandlerResponse import HandlerResponse
from Config.Exceptions import BadCommandUsage from Config.Exceptions import BadCommandUsage
from Parallelism.ProcessManager import ProcessManager
class LoopHandler(AbstractHandler): class LoopHandler(AbstractHandler):
@@ -10,30 +11,41 @@ class LoopHandler(AbstractHandler):
super().__init__(ctx, bot) super().__init__(ctx, bot)
async def run(self, args: str) -> HandlerResponse: async def run(self, args: str) -> HandlerResponse:
if args == '' or args is None: # Get the current process of the guild
self.player.playlist.loop_all() processManager = ProcessManager()
embed = self.embeds.LOOP_ALL_ACTIVATED() processContext = processManager.getRunningPlayerContext(self.guild)
return HandlerResponse(self.ctx, embed) if not processContext:
args = args.lower()
if self.player.playlist.getCurrentSong() is None:
embed = self.embeds.NOT_PLAYING() embed = self.embeds.NOT_PLAYING()
error = BadCommandUsage() error = BadCommandUsage()
return HandlerResponse(self.ctx, embed, error) return HandlerResponse(self.ctx, embed, error)
if args == 'one': playlist = processContext.getPlaylist()
self.player.playlist.loop_one()
embed = self.embeds.LOOP_ONE_ACTIVATED() with processContext.getLock():
return HandlerResponse(self.ctx, embed) if args == '' or args is None:
elif args == 'all': playlist.loop_all()
self.player.playlist.loop_all() embed = self.embeds.LOOP_ALL_ACTIVATED()
embed = self.embeds.LOOP_ALL_ACTIVATED() return HandlerResponse(self.ctx, embed)
return HandlerResponse(self.ctx, embed)
elif args == 'off': args = args.lower()
self.player.playlist.loop_off() if playlist.getCurrentSong() is None:
embed = self.embeds.LOOP_DISABLE() embed = self.embeds.NOT_PLAYING()
return HandlerResponse(self.ctx, embed) error = BadCommandUsage()
else: return HandlerResponse(self.ctx, embed, error)
error = BadCommandUsage()
embed = self.embeds.BAD_LOOP_USE() if args == 'one':
return HandlerResponse(self.ctx, embed, error) playlist.loop_one()
embed = self.embeds.LOOP_ONE_ACTIVATED()
return HandlerResponse(self.ctx, embed)
elif args == 'all':
playlist.loop_all()
embed = self.embeds.LOOP_ALL_ACTIVATED()
return HandlerResponse(self.ctx, embed)
elif args == 'off':
playlist.loop_off()
embed = self.embeds.LOOP_DISABLE()
return HandlerResponse(self.ctx, embed)
else:
error = BadCommandUsage()
embed = self.embeds.BAD_LOOP_USE()
return HandlerResponse(self.ctx, embed, error)

View File

@@ -3,6 +3,7 @@ from discord import Client
from Handlers.AbstractHandler import AbstractHandler from Handlers.AbstractHandler import AbstractHandler
from Handlers.HandlerResponse import HandlerResponse from Handlers.HandlerResponse import HandlerResponse
from Utils.Cleaner import Cleaner from Utils.Cleaner import Cleaner
from Parallelism.ProcessManager import ProcessManager
class NowPlayingHandler(AbstractHandler): class NowPlayingHandler(AbstractHandler):
@@ -11,16 +12,24 @@ class NowPlayingHandler(AbstractHandler):
self.__cleaner = Cleaner() self.__cleaner = Cleaner()
async def run(self) -> HandlerResponse: async def run(self) -> HandlerResponse:
if not self.player.playing: # Get the current process of the guild
processManager = ProcessManager()
processContext = processManager.getRunningPlayerContext(self.guild)
if not processContext:
embed = self.embeds.NOT_PLAYING() embed = self.embeds.NOT_PLAYING()
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)
if self.player.playlist.isLoopingOne(): playlist = processContext.getPlaylist()
if playlist.getCurrentSong() is None:
embed = self.embeds.NOT_PLAYING()
return HandlerResponse(self.ctx, embed)
if playlist.isLoopingOne():
title = self.messages.ONE_SONG_LOOPING title = self.messages.ONE_SONG_LOOPING
else: else:
title = self.messages.SONG_PLAYING title = self.messages.SONG_PLAYING
await self.__cleaner.clean_messages(self.ctx, self.config.CLEANER_MESSAGES_QUANT) await self.__cleaner.clean_messages(self.ctx, self.config.CLEANER_MESSAGES_QUANT)
info = self.player.playlist.getCurrentSong().info info = playlist.getCurrentSong().info
embed = self.embeds.SONG_INFO(info, title) embed = self.embeds.SONG_INFO(info, title)
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)

View File

@@ -58,7 +58,7 @@ class PlayHandler(AbstractHandler):
response = HandlerResponse(self.ctx, embed) response = HandlerResponse(self.ctx, embed)
# Get the process context for the current guild # Get the process context for the current guild
manager = ProcessManager(self.bot) manager = ProcessManager()
processContext = manager.getPlayerContext(self.guild, self.ctx) processContext = manager.getPlayerContext(self.guild, self.ctx)
# Add the downloaded song to the process playlist # Add the downloaded song to the process playlist
# All access to shared memory should be protect by acquire the Lock # All access to shared memory should be protect by acquire the Lock

View File

@@ -4,6 +4,8 @@ from discord import Client
from Handlers.AbstractHandler import AbstractHandler from Handlers.AbstractHandler import AbstractHandler
from Handlers.HandlerResponse import HandlerResponse from Handlers.HandlerResponse import HandlerResponse
from Config.Exceptions import BadCommandUsage, VulkanError, ErrorRemoving, InvalidInput, NumberRequired from Config.Exceptions import BadCommandUsage, VulkanError, ErrorRemoving, InvalidInput, NumberRequired
from Music.Playlist import Playlist
from Parallelism.ProcessManager import ProcessManager
class RemoveHandler(AbstractHandler): class RemoveHandler(AbstractHandler):
@@ -11,24 +13,34 @@ class RemoveHandler(AbstractHandler):
super().__init__(ctx, bot) super().__init__(ctx, bot)
async def run(self, position: str) -> HandlerResponse: async def run(self, position: str) -> HandlerResponse:
if not self.player.playlist: # Get the current process of the guild
processManager = ProcessManager()
processContext = processManager.getRunningPlayerContext(self.guild)
if not processContext:
# Clear the playlist
embed = self.embeds.NOT_PLAYING() embed = self.embeds.NOT_PLAYING()
error = BadCommandUsage() error = BadCommandUsage()
return HandlerResponse(self.ctx, embed, error) return HandlerResponse(self.ctx, embed, error)
error = self.__validate_input(position) playlist = processContext.getPlaylist()
if playlist.getCurrentSong() is None:
embed = self.embeds.NOT_PLAYING()
error = BadCommandUsage()
return HandlerResponse(self.ctx, embed, error)
error = self.__validateInput(position)
if error: if error:
embed = self.embeds.ERROR_EMBED(error.message) embed = self.embeds.ERROR_EMBED(error.message)
return HandlerResponse(self.ctx, embed, error) return HandlerResponse(self.ctx, embed, error)
position = self.__sanitize_input(position) position = self.__sanitizeInput(playlist, position)
if not self.player.playlist.validate_position(position): if not playlist.validate_position(position):
error = InvalidInput() error = InvalidInput()
embed = self.embeds.PLAYLIST_RANGE_ERROR() embed = self.embeds.PLAYLIST_RANGE_ERROR()
return HandlerResponse(self.ctx, embed, error) return HandlerResponse(self.ctx, embed, error)
try: try:
song = self.player.playlist.remove_song(position) song = playlist.remove_song(position)
name = song.title if song.title else song.identifier name = song.title if song.title else song.identifier
embed = self.embeds.SONG_REMOVED(name) embed = self.embeds.SONG_REMOVED(name)
@@ -38,15 +50,15 @@ class RemoveHandler(AbstractHandler):
embed = self.embeds.ERROR_REMOVING() embed = self.embeds.ERROR_REMOVING()
return HandlerResponse(self.ctx, embed, error) return HandlerResponse(self.ctx, embed, error)
def __validate_input(self, position: str) -> Union[VulkanError, None]: def __validateInput(self, position: str) -> Union[VulkanError, None]:
try: try:
position = int(position) position = int(position)
except: except:
return NumberRequired(self.messages.ERROR_NUMBER) return NumberRequired(self.messages.ERROR_NUMBER)
def __sanitize_input(self, position: str) -> int: def __sanitizeInput(self, playlist: Playlist, position: str) -> int:
position = int(position) position = int(position)
if position == -1: if position == -1:
position = len(self.player.playlist) position = len(playlist)
return position return position

View File

@@ -2,19 +2,21 @@ from discord.ext.commands import Context
from discord import Client from discord import Client
from Handlers.AbstractHandler import AbstractHandler from Handlers.AbstractHandler import AbstractHandler
from Handlers.HandlerResponse import HandlerResponse from Handlers.HandlerResponse import HandlerResponse
from Handlers.PlayersController import PlayersController from Parallelism.ProcessManager import ProcessManager
from Parallelism.Commands import VCommands, VCommandsType
class ResetHandler(AbstractHandler): class ResetHandler(AbstractHandler):
def __init__(self, ctx: Context, bot: Client) -> None: def __init__(self, ctx: Context, bot: Client) -> None:
super().__init__(ctx, bot) super().__init__(ctx, bot)
self.__controller = PlayersController(self.bot)
async def run(self) -> HandlerResponse: async def run(self) -> HandlerResponse:
try: # Get the current process of the guild
await self.player.force_stop() processManager = ProcessManager()
await self.bot_member.move_to(None) processContext = processManager.getRunningPlayerContext(self.guild)
self.__controller.reset_player(self.guild) if processContext:
return HandlerResponse(self.ctx) command = VCommands(VCommandsType.RESET, None)
except Exception as e: queue = processContext.getQueue()
print(f'DEVELOPER NOTE -> Reset Error: {e}') queue.put(command)
return HandlerResponse(self.ctx)

View File

@@ -1,27 +1,33 @@
import asyncio
from discord.ext.commands import Context from discord.ext.commands import Context
from discord import Client from discord import Client
from Handlers.AbstractHandler import AbstractHandler from Handlers.AbstractHandler import AbstractHandler
from Handlers.HandlerResponse import HandlerResponse from Handlers.HandlerResponse import HandlerResponse
from Config.Exceptions import UnknownError from Config.Exceptions import UnknownError
from Music.Downloader import Downloader from Parallelism.ProcessManager import ProcessManager
class ShuffleHandler(AbstractHandler): class ShuffleHandler(AbstractHandler):
def __init__(self, ctx: Context, bot: Client) -> None: def __init__(self, ctx: Context, bot: Client) -> None:
super().__init__(ctx, bot) super().__init__(ctx, bot)
self.__down = Downloader()
async def run(self) -> HandlerResponse: async def run(self) -> HandlerResponse:
try: processManager = ProcessManager()
self.player.playlist.shuffle() processContext = processManager.getRunningPlayerContext(self.guild)
songs = self.player.playlist.getSongsToPreload() if processContext:
try:
with processContext.getLock():
playlist = processContext.getPlaylist()
playlist.shuffle()
asyncio.create_task(self.__down.preload(songs)) embed = self.embeds.SONGS_SHUFFLED()
embed = self.embeds.SONGS_SHUFFLED() return HandlerResponse(self.ctx, embed)
except Exception as e:
print(f'DEVELOPER NOTE -> Error Shuffling: {e}')
error = UnknownError()
embed = self.embeds.ERROR_SHUFFLING()
return HandlerResponse(self.ctx, embed, error)
else:
embed = self.embeds.NOT_PLAYING()
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)
except Exception as e:
print(f'DEVELOPER NOTE -> Error Shuffling: {e}')
error = UnknownError()
embed = self.embeds.ERROR_SHUFFLING()
return HandlerResponse(self.ctx, embed, error)

View File

@@ -10,6 +10,7 @@ class VCommandsType(Enum):
CONTEXT = 'Context' CONTEXT = 'Context'
PLAY = 'Play' PLAY = 'Play'
STOP = 'Stop' STOP = 'Stop'
RESET = 'Reset'
class VCommands: class VCommands:

View File

@@ -1,10 +1,10 @@
import asyncio import asyncio
from os import listdir from os import listdir
from discord import Intents, User from discord import Intents, User, Member
from asyncio import AbstractEventLoop, Semaphore from asyncio import AbstractEventLoop, Semaphore
from multiprocessing import Process, Queue from multiprocessing import Process, Queue
from threading import Lock, Thread from threading import Lock, Thread
from typing import Callable from typing import Callable, List
from discord import Client, Guild, FFmpegPCMAudio, VoiceChannel, TextChannel from discord import Client, Guild, FFmpegPCMAudio, VoiceChannel, TextChannel
from discord.ext.commands import Context from discord.ext.commands import Context
from Music.Playlist import Playlist from Music.Playlist import Playlist
@@ -53,6 +53,7 @@ class PlayerProcess(Process):
self.__voiceChannel: VoiceChannel = None self.__voiceChannel: VoiceChannel = None
self.__textChannel: TextChannel = None self.__textChannel: TextChannel = None
self.__author: User = None self.__author: User = None
self.__botMember: Member = None
self.__configs: Configs = None self.__configs: Configs = None
self.__playing = False self.__playing = False
@@ -79,6 +80,7 @@ class PlayerProcess(Process):
self.__voiceChannel = self.__bot.get_channel(self.__voiceChannelID) self.__voiceChannel = self.__bot.get_channel(self.__voiceChannelID)
self.__textChannel = self.__bot.get_channel(self.__textChannelID) self.__textChannel = self.__bot.get_channel(self.__textChannelID)
self.__author = self.__bot.get_channel(self.__authorID) self.__author = self.__bot.get_channel(self.__authorID)
self.__botMember = self.__getBotMember()
# Connect to voice Channel # Connect to voice Channel
await self.__connectToVoiceChannel() await self.__connectToVoiceChannel()
@@ -93,6 +95,8 @@ class PlayerProcess(Process):
# Try to acquire a semaphore, it'll be release when timeout function trigger, we use the Semaphore # Try to acquire a semaphore, it'll be release when timeout function trigger, we use the Semaphore
# from the asyncio lib to not block the event loop # from the asyncio lib to not block the event loop
await self.__semStopPlaying.acquire() await self.__semStopPlaying.acquire()
# In this point the process should finalize
self.__timer.cancel()
async def __playPlaylistSongs(self) -> None: async def __playPlaylistSongs(self) -> None:
print(f'Playing: {self.__playing}') print(f'Playing: {self.__playing}')
@@ -153,6 +157,8 @@ class PlayerProcess(Process):
self.__resume() self.__resume()
elif type == VCommandsType.SKIP: elif type == VCommandsType.SKIP:
self.__skip() self.__skip()
elif type == VCommandsType.RESET:
self.__loop.create_task(self.__reset())
elif type == VCommandsType.STOP: elif type == VCommandsType.STOP:
self.__loop.create_task(self.__stop()) self.__loop.create_task(self.__stop())
else: else:
@@ -163,6 +169,18 @@ class PlayerProcess(Process):
if self.__guild.voice_client.is_playing(): if self.__guild.voice_client.is_playing():
self.__guild.voice_client.pause() self.__guild.voice_client.pause()
async def __reset(self) -> None:
if self.__guild.voice_client is None:
return
# Reset the bot
self.__guild.voice_client.stop()
await self.__guild.voice_client.disconnect()
self.__playlist.clear()
self.__playlist.loop_off()
await self.__botMember.move_to(None)
# Release semaphore to finish the current player process
self.__semStopPlaying.release()
async def __stop(self) -> None: async def __stop(self) -> None:
if self.__guild.voice_client is not None: if self.__guild.voice_client is not None:
if self.__guild.voice_client.is_connected(): if self.__guild.voice_client.is_connected():
@@ -236,15 +254,12 @@ class PlayerProcess(Process):
try: try:
print('TimeoutHandler') print('TimeoutHandler')
if self.__guild.voice_client is None: if self.__guild.voice_client is None:
print('return')
return return
if self.__guild.voice_client.is_playing() or self.__guild.voice_client.is_paused(): if self.__guild.voice_client.is_playing() or self.__guild.voice_client.is_paused():
print('Resetting')
self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop) self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop)
elif self.__guild.voice_client.is_connected(): elif self.__guild.voice_client.is_connected():
print('Finish')
with self.__lock: with self.__lock:
self.__playlist.clear() self.__playlist.clear()
self.__playlist.loop_off() self.__playlist.loop_off()
@@ -270,3 +285,9 @@ class PlayerProcess(Process):
return True return True
except: except:
return False return False
def __getBotMember(self) -> Member:
guild_members: List[Member] = self.__guild.members
for member in guild_members:
if member.id == self.__bot.user.id:
return member