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')
self.CLEANER_MESSAGES_QUANT = 5
self.COMMANDS_PATH = 'Commands'
self.COMMANDS_PATH = 'DiscordCogs'
self.VC_TIMEOUT = 600
self.MAX_PLAYLIST_LENGTH = 50

View File

@ -42,6 +42,8 @@ class Messages(Singleton):
self.LOOP_DISABLE = '➡️ Loop 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.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.ERROR_SHUFFLING = '❌ Error while shuffling the songs'

View File

@ -21,7 +21,6 @@ from Handlers.QueueHandler import QueueHandler
from Handlers.LoopHandler import LoopHandler
from Views.EmoteView import EmoteView
from Views.EmbedView import EmbedView
from Parallelism.ProcessManager import ProcessManager
helper = Helper()
@ -120,7 +119,7 @@ class MusicCog(commands.Cog):
await view1.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:
controller = HistoryHandler(ctx, self.__bot)
@ -158,7 +157,7 @@ class MusicCog(commands.Cog):
await view1.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:
controller = ShuffleHandler(ctx, self.__bot)

View File

@ -13,7 +13,7 @@ class RandomCog(Cog):
def __init__(self, bot: Client):
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:
try:
arg = int(arg)

View File

@ -3,6 +3,7 @@ from discord import Client
from Handlers.AbstractHandler import AbstractHandler
from Handlers.HandlerResponse import HandlerResponse
from Utils.Utils import Utils
from Parallelism.ProcessManager import ProcessManager
class HistoryHandler(AbstractHandler):
@ -10,11 +11,18 @@ class HistoryHandler(AbstractHandler):
super().__init__(ctx, bot)
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:
text = self.messages.HISTORY_EMPTY
else:
text = f'\n📜 History Length: {len(history)} | Max: {self.config.MAX_SONGS_HISTORY}\n'
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.HandlerResponse import HandlerResponse
from Config.Exceptions import BadCommandUsage
from Parallelism.ProcessManager import ProcessManager
class LoopHandler(AbstractHandler):
@ -10,30 +11,41 @@ class LoopHandler(AbstractHandler):
super().__init__(ctx, bot)
async def run(self, args: str) -> HandlerResponse:
if args == '' or args is None:
self.player.playlist.loop_all()
embed = self.embeds.LOOP_ALL_ACTIVATED()
return HandlerResponse(self.ctx, embed)
args = args.lower()
if self.player.playlist.getCurrentSong() is None:
# Get the current process of the guild
processManager = ProcessManager()
processContext = processManager.getRunningPlayerContext(self.guild)
if not processContext:
embed = self.embeds.NOT_PLAYING()
error = BadCommandUsage()
return HandlerResponse(self.ctx, embed, error)
if args == 'one':
self.player.playlist.loop_one()
embed = self.embeds.LOOP_ONE_ACTIVATED()
return HandlerResponse(self.ctx, embed)
elif args == 'all':
self.player.playlist.loop_all()
embed = self.embeds.LOOP_ALL_ACTIVATED()
return HandlerResponse(self.ctx, embed)
elif args == 'off':
self.player.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)
playlist = processContext.getPlaylist()
with processContext.getLock():
if args == '' or args is None:
playlist.loop_all()
embed = self.embeds.LOOP_ALL_ACTIVATED()
return HandlerResponse(self.ctx, embed)
args = args.lower()
if playlist.getCurrentSong() is None:
embed = self.embeds.NOT_PLAYING()
error = BadCommandUsage()
return HandlerResponse(self.ctx, embed, error)
if args == 'one':
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.HandlerResponse import HandlerResponse
from Utils.Cleaner import Cleaner
from Parallelism.ProcessManager import ProcessManager
class NowPlayingHandler(AbstractHandler):
@ -11,16 +12,24 @@ class NowPlayingHandler(AbstractHandler):
self.__cleaner = Cleaner()
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()
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
else:
title = self.messages.SONG_PLAYING
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)
return HandlerResponse(self.ctx, embed)

View File

@ -58,7 +58,7 @@ class PlayHandler(AbstractHandler):
response = HandlerResponse(self.ctx, embed)
# Get the process context for the current guild
manager = ProcessManager(self.bot)
manager = ProcessManager()
processContext = manager.getPlayerContext(self.guild, self.ctx)
# Add the downloaded song to the process playlist
# 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.HandlerResponse import HandlerResponse
from Config.Exceptions import BadCommandUsage, VulkanError, ErrorRemoving, InvalidInput, NumberRequired
from Music.Playlist import Playlist
from Parallelism.ProcessManager import ProcessManager
class RemoveHandler(AbstractHandler):
@ -11,24 +13,34 @@ class RemoveHandler(AbstractHandler):
super().__init__(ctx, bot)
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()
error = BadCommandUsage()
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:
embed = self.embeds.ERROR_EMBED(error.message)
return HandlerResponse(self.ctx, embed, error)
position = self.__sanitize_input(position)
if not self.player.playlist.validate_position(position):
position = self.__sanitizeInput(playlist, position)
if not playlist.validate_position(position):
error = InvalidInput()
embed = self.embeds.PLAYLIST_RANGE_ERROR()
return HandlerResponse(self.ctx, embed, error)
try:
song = self.player.playlist.remove_song(position)
song = playlist.remove_song(position)
name = song.title if song.title else song.identifier
embed = self.embeds.SONG_REMOVED(name)
@ -38,15 +50,15 @@ class RemoveHandler(AbstractHandler):
embed = self.embeds.ERROR_REMOVING()
return HandlerResponse(self.ctx, embed, error)
def __validate_input(self, position: str) -> Union[VulkanError, None]:
def __validateInput(self, position: str) -> Union[VulkanError, None]:
try:
position = int(position)
except:
return NumberRequired(self.messages.ERROR_NUMBER)
def __sanitize_input(self, position: str) -> int:
def __sanitizeInput(self, playlist: Playlist, position: str) -> int:
position = int(position)
if position == -1:
position = len(self.player.playlist)
position = len(playlist)
return position

View File

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

View File

@ -1,27 +1,33 @@
import asyncio
from discord.ext.commands import Context
from discord import Client
from Handlers.AbstractHandler import AbstractHandler
from Handlers.HandlerResponse import HandlerResponse
from Config.Exceptions import UnknownError
from Music.Downloader import Downloader
from Parallelism.ProcessManager import ProcessManager
class ShuffleHandler(AbstractHandler):
def __init__(self, ctx: Context, bot: Client) -> None:
super().__init__(ctx, bot)
self.__down = Downloader()
async def run(self) -> HandlerResponse:
try:
self.player.playlist.shuffle()
songs = self.player.playlist.getSongsToPreload()
processManager = ProcessManager()
processContext = processManager.getRunningPlayerContext(self.guild)
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)
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'
PLAY = 'Play'
STOP = 'Stop'
RESET = 'Reset'
class VCommands:

View File

@ -1,10 +1,10 @@
import asyncio
from os import listdir
from discord import Intents, User
from discord import Intents, User, Member
from asyncio import AbstractEventLoop, Semaphore
from multiprocessing import Process, Queue
from threading import Lock, Thread
from typing import Callable
from typing import Callable, List
from discord import Client, Guild, FFmpegPCMAudio, VoiceChannel, TextChannel
from discord.ext.commands import Context
from Music.Playlist import Playlist
@ -53,6 +53,7 @@ class PlayerProcess(Process):
self.__voiceChannel: VoiceChannel = None
self.__textChannel: TextChannel = None
self.__author: User = None
self.__botMember: Member = None
self.__configs: Configs = None
self.__playing = False
@ -79,6 +80,7 @@ class PlayerProcess(Process):
self.__voiceChannel = self.__bot.get_channel(self.__voiceChannelID)
self.__textChannel = self.__bot.get_channel(self.__textChannelID)
self.__author = self.__bot.get_channel(self.__authorID)
self.__botMember = self.__getBotMember()
# Connect to voice Channel
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
# from the asyncio lib to not block the event loop
await self.__semStopPlaying.acquire()
# In this point the process should finalize
self.__timer.cancel()
async def __playPlaylistSongs(self) -> None:
print(f'Playing: {self.__playing}')
@ -153,6 +157,8 @@ class PlayerProcess(Process):
self.__resume()
elif type == VCommandsType.SKIP:
self.__skip()
elif type == VCommandsType.RESET:
self.__loop.create_task(self.__reset())
elif type == VCommandsType.STOP:
self.__loop.create_task(self.__stop())
else:
@ -163,6 +169,18 @@ class PlayerProcess(Process):
if self.__guild.voice_client.is_playing():
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:
if self.__guild.voice_client is not None:
if self.__guild.voice_client.is_connected():
@ -236,15 +254,12 @@ class PlayerProcess(Process):
try:
print('TimeoutHandler')
if self.__guild.voice_client is None:
print('return')
return
if self.__guild.voice_client.is_playing() or self.__guild.voice_client.is_paused():
print('Resetting')
self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop)
elif self.__guild.voice_client.is_connected():
print('Finish')
with self.__lock:
self.__playlist.clear()
self.__playlist.loop_off()
@ -270,3 +285,9 @@ class PlayerProcess(Process):
return True
except:
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