mirror of
https://github.com/RafaelSolVargas/Vulkan.git
synced 2025-10-29 16:57:23 +00:00
Merge pull request #21 from RafaelSolVargas/usingProcess
Upgrading the architecture to use both asyncio and multiprocessing modules
This commit is contained in:
commit
48d7166386
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
.vscode
|
||||
assets/
|
||||
__pycache__
|
||||
.env
|
||||
|
||||
@ -1,197 +0,0 @@
|
||||
from discord import Guild, Client
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
from Config.Helper import Helper
|
||||
from Controllers.ClearController import ClearController
|
||||
from Controllers.MoveController import MoveController
|
||||
from Controllers.NowPlayingController import NowPlayingController
|
||||
from Controllers.PlayController import PlayController
|
||||
from Controllers.PlayerController import PlayersController
|
||||
from Controllers.PrevController import PrevController
|
||||
from Controllers.RemoveController import RemoveController
|
||||
from Controllers.ResetController import ResetController
|
||||
from Controllers.ShuffleController import ShuffleController
|
||||
from Utils.Cleaner import Cleaner
|
||||
from Controllers.SkipController import SkipController
|
||||
from Controllers.PauseController import PauseController
|
||||
from Controllers.StopController import StopController
|
||||
from Controllers.ResumeController import ResumeController
|
||||
from Controllers.HistoryController import HistoryController
|
||||
from Controllers.QueueController import QueueController
|
||||
from Controllers.LoopController import LoopController
|
||||
from Views.EmoteView import EmoteView
|
||||
from Views.EmbedView import EmbedView
|
||||
|
||||
|
||||
helper = Helper()
|
||||
|
||||
|
||||
class Music(commands.Cog):
|
||||
def __init__(self, bot) -> None:
|
||||
self.__bot: Client = bot
|
||||
self.__cleaner = Cleaner(self.__bot)
|
||||
self.__controller = PlayersController(self.__bot)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_ready(self) -> None:
|
||||
self.__controller = PlayersController(self.__bot)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_guild_join(self, guild: Guild) -> None:
|
||||
self.__controller.create_player(guild)
|
||||
|
||||
@commands.command(name="play", help=helper.HELP_PLAY, description=helper.HELP_PLAY_LONG, aliases=['p', 'tocar'])
|
||||
async def play(self, ctx: Context, *args) -> None:
|
||||
controller = PlayController(ctx, self.__bot)
|
||||
|
||||
response = await controller.run(args)
|
||||
if response is not None:
|
||||
view1 = EmbedView(response)
|
||||
view2 = EmoteView(response)
|
||||
await view1.run()
|
||||
await view2.run()
|
||||
|
||||
@commands.command(name="queue", help=helper.HELP_QUEUE, description=helper.HELP_QUEUE_LONG, aliases=['q', 'fila'])
|
||||
async def queue(self, ctx: Context) -> None:
|
||||
controller = QueueController(ctx, self.__bot)
|
||||
|
||||
response = await controller.run()
|
||||
view2 = EmbedView(response)
|
||||
await view2.run()
|
||||
|
||||
@commands.command(name="skip", help=helper.HELP_SKIP, description=helper.HELP_SKIP_LONG, aliases=['s', 'pular'])
|
||||
async def skip(self, ctx: Context) -> None:
|
||||
controller = SkipController(ctx, self.__bot)
|
||||
|
||||
response = await controller.run()
|
||||
if response.success:
|
||||
view = EmoteView(response)
|
||||
else:
|
||||
view = EmbedView(response)
|
||||
|
||||
await view.run()
|
||||
|
||||
@commands.command(name='stop', help=helper.HELP_STOP, description=helper.HELP_STOP_LONG, aliases=['parar'])
|
||||
async def stop(self, ctx: Context) -> None:
|
||||
controller = StopController(ctx, self.__bot)
|
||||
|
||||
response = await controller.run()
|
||||
if response.success:
|
||||
view = EmoteView(response)
|
||||
else:
|
||||
view = EmbedView(response)
|
||||
|
||||
await view.run()
|
||||
|
||||
@commands.command(name='pause', help=helper.HELP_PAUSE, description=helper.HELP_PAUSE_LONG, aliases=['pausar'])
|
||||
async def pause(self, ctx: Context) -> None:
|
||||
controller = PauseController(ctx, self.__bot)
|
||||
|
||||
response = await controller.run()
|
||||
view1 = EmoteView(response)
|
||||
view2 = EmbedView(response)
|
||||
await view1.run()
|
||||
await view2.run()
|
||||
|
||||
@commands.command(name='resume', help=helper.HELP_RESUME, description=helper.HELP_RESUME_LONG, aliases=['soltar'])
|
||||
async def resume(self, ctx: Context) -> None:
|
||||
controller = ResumeController(ctx, self.__bot)
|
||||
|
||||
response = await controller.run()
|
||||
view1 = EmoteView(response)
|
||||
view2 = EmbedView(response)
|
||||
await view1.run()
|
||||
await view2.run()
|
||||
|
||||
@commands.command(name='prev', help=helper.HELP_PREV, description=helper.HELP_PREV_LONG, aliases=['anterior'])
|
||||
async def prev(self, ctx: Context) -> None:
|
||||
controller = PrevController(ctx, self.__bot)
|
||||
|
||||
response = await controller.run()
|
||||
if response is not None:
|
||||
view1 = EmbedView(response)
|
||||
view2 = EmoteView(response)
|
||||
await view1.run()
|
||||
await view2.run()
|
||||
|
||||
@commands.command(name='history', help=helper.HELP_HISTORY, description=helper.HELP_HISTORY_LONG, aliases=['historico'])
|
||||
async def history(self, ctx: Context) -> None:
|
||||
controller = HistoryController(ctx, self.__bot)
|
||||
|
||||
response = await controller.run()
|
||||
view1 = EmbedView(response)
|
||||
view2 = EmoteView(response)
|
||||
await view1.run()
|
||||
await view2.run()
|
||||
|
||||
@commands.command(name='loop', help=helper.HELP_LOOP, description=helper.HELP_LOOP_LONG, aliases=['l', 'repeat'])
|
||||
async def loop(self, ctx: Context, args='') -> None:
|
||||
controller = LoopController(ctx, self.__bot)
|
||||
|
||||
response = await controller.run(args)
|
||||
view1 = EmoteView(response)
|
||||
view2 = EmbedView(response)
|
||||
await view1.run()
|
||||
await view2.run()
|
||||
|
||||
@commands.command(name='clear', help=helper.HELP_CLEAR, description=helper.HELP_CLEAR_LONG, aliases=['c', 'limpar'])
|
||||
async def clear(self, ctx: Context) -> None:
|
||||
controller = ClearController(ctx, self.__bot)
|
||||
|
||||
response = await controller.run()
|
||||
view = EmoteView(response)
|
||||
await view.run()
|
||||
|
||||
@commands.command(name='np', help=helper.HELP_NP, description=helper.HELP_NP_LONG, aliases=['playing', 'now'])
|
||||
async def now_playing(self, ctx: Context) -> None:
|
||||
controller = NowPlayingController(ctx, self.__bot)
|
||||
|
||||
response = await controller.run()
|
||||
view1 = EmbedView(response)
|
||||
view2 = EmoteView(response)
|
||||
await view1.run()
|
||||
await view2.run()
|
||||
|
||||
@commands.command(name='shuffle', help=helper.HELP_SHUFFLE, description=helper.HELP_SHUFFLE_LONG, aliases=['aleatorio'])
|
||||
async def shuffle(self, ctx: Context) -> None:
|
||||
controller = ShuffleController(ctx, self.__bot)
|
||||
|
||||
response = await controller.run()
|
||||
view1 = EmbedView(response)
|
||||
view2 = EmoteView(response)
|
||||
await view1.run()
|
||||
await view2.run()
|
||||
|
||||
@commands.command(name='move', help=helper.HELP_MOVE, description=helper.HELP_MOVE_LONG, aliases=['m', 'mover'])
|
||||
async def move(self, ctx: Context, pos1, pos2='1') -> None:
|
||||
controller = MoveController(ctx, self.__bot)
|
||||
|
||||
response = await controller.run(pos1, pos2)
|
||||
view1 = EmbedView(response)
|
||||
view2 = EmoteView(response)
|
||||
await view1.run()
|
||||
await view2.run()
|
||||
|
||||
@commands.command(name='remove', help=helper.HELP_REMOVE, description=helper.HELP_REMOVE_LONG, aliases=['remover'])
|
||||
async def remove(self, ctx: Context, position) -> None:
|
||||
controller = RemoveController(ctx, self.__bot)
|
||||
|
||||
response = await controller.run(position)
|
||||
view1 = EmbedView(response)
|
||||
view2 = EmoteView(response)
|
||||
await view1.run()
|
||||
await view2.run()
|
||||
|
||||
@commands.command(name='reset', help=helper.HELP_RESET, description=helper.HELP_RESET_LONG, aliases=['resetar'])
|
||||
async def reset(self, ctx: Context) -> None:
|
||||
controller = ResetController(ctx, self.__bot)
|
||||
|
||||
response = await controller.run()
|
||||
view1 = EmbedView(response)
|
||||
view2 = EmoteView(response)
|
||||
await view1.run()
|
||||
await view2.run()
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(Music(bot))
|
||||
@ -16,12 +16,13 @@ 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.ACQUIRE_LOCK_TIMEOUT = 10
|
||||
self.COMMANDS_PATH = 'DiscordCogs'
|
||||
self.VC_TIMEOUT = 600
|
||||
|
||||
self.MAX_PLAYLIST_LENGTH = 50
|
||||
self.MAX_PLAYLIST_FORCED_LENGTH = 5
|
||||
self.MAX_PRELOAD_SONGS = 10
|
||||
self.MAX_PRELOAD_SONGS = 15
|
||||
self.MAX_SONGS_HISTORY = 15
|
||||
|
||||
self.INVITE_MESSAGE = """To invite Vulkan to your own server, click [here]({}).
|
||||
|
||||
@ -14,8 +14,8 @@ class Messages(Singleton):
|
||||
self.SONGINFO_REQUESTER = 'Requester: '
|
||||
self.SONGINFO_POSITION = 'Position: '
|
||||
|
||||
self.SONGS_ADDED = 'You added {} songs to the queue'
|
||||
self.SONG_ADDED = 'You added the song `{}` to the queue'
|
||||
self.SONGS_ADDED = 'Downloading `{}` songs to add to the queue'
|
||||
self.SONG_ADDED = 'Downloading the song `{}` to add to the queue'
|
||||
self.SONG_ADDED_TWO = '🎧 Song added to the queue'
|
||||
self.SONG_PLAYING = '🎧 Song playing now'
|
||||
self.SONG_PLAYER = '🎧 Song Player'
|
||||
@ -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'
|
||||
@ -56,12 +58,14 @@ class Messages(Singleton):
|
||||
self.PLAYER_NOT_PLAYING = f'❌ No song playing. Use {configs.BOT_PREFIX}play to start the player'
|
||||
self.IMPOSSIBLE_MOVE = 'That is impossible :('
|
||||
self.ERROR_TITLE = 'Error :-('
|
||||
self.COMMAND_NOT_FOUND_TITLE = 'This is strange :-('
|
||||
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.DOWNLOADING_ERROR = '❌ An error occurred while downloading'
|
||||
self.DOWNLOADING_ERROR = "❌ It's impossible to download and play this video"
|
||||
self.EXTRACTING_ERROR = '❌ An error ocurred while searching for the songs'
|
||||
|
||||
self.ERROR_IN_PROCESS = "❌ Due to a internal error your player was restarted, skipping the song."
|
||||
self.MY_ERROR_BAD_COMMAND = 'This string serves to verify if some error was raised by myself on purpose'
|
||||
self.BAD_COMMAND_TITLE = 'Misuse of command'
|
||||
self.BAD_COMMAND = f'❌ Bad usage of this command, type {configs.BOT_PREFIX}help "command" to understand the command better'
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client
|
||||
from Controllers.AbstractController import AbstractController
|
||||
from Controllers.ControllerResponse import ControllerResponse
|
||||
|
||||
|
||||
class ClearController(AbstractController):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
|
||||
async def run(self) -> ControllerResponse:
|
||||
self.player.playlist.clear()
|
||||
return ControllerResponse(self.ctx)
|
||||
@ -1,24 +0,0 @@
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client
|
||||
from Controllers.AbstractController import AbstractController
|
||||
from Controllers.ControllerResponse import ControllerResponse
|
||||
from Utils.Utils import Utils
|
||||
|
||||
|
||||
class HistoryController(AbstractController):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
|
||||
async def run(self) -> ControllerResponse:
|
||||
history = self.player.playlist.songs_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):
|
||||
text += f"**`{pos}` - ** {song.title} - `{Utils.format_time(song.duration)}`\n"
|
||||
|
||||
embed = self.embeds.HISTORY(text)
|
||||
return ControllerResponse(self.ctx, embed)
|
||||
@ -1,39 +0,0 @@
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client
|
||||
from Controllers.AbstractController import AbstractController
|
||||
from Controllers.ControllerResponse import ControllerResponse
|
||||
from Exceptions.Exceptions import BadCommandUsage
|
||||
|
||||
|
||||
class LoopController(AbstractController):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
|
||||
async def run(self, args: str) -> ControllerResponse:
|
||||
if args == '' or args is None:
|
||||
self.player.playlist.loop_all()
|
||||
embed = self.embeds.LOOP_ALL_ACTIVATED()
|
||||
return ControllerResponse(self.ctx, embed)
|
||||
|
||||
args = args.lower()
|
||||
if self.player.playlist.current is None:
|
||||
embed = self.embeds.NOT_PLAYING()
|
||||
error = BadCommandUsage()
|
||||
return ControllerResponse(self.ctx, embed, error)
|
||||
|
||||
if args == 'one':
|
||||
self.player.playlist.loop_one()
|
||||
embed = self.embeds.LOOP_ONE_ACTIVATED()
|
||||
return ControllerResponse(self.ctx, embed)
|
||||
elif args == 'all':
|
||||
self.player.playlist.loop_all()
|
||||
embed = self.embeds.LOOP_ALL_ACTIVATED()
|
||||
return ControllerResponse(self.ctx, embed)
|
||||
elif args == 'off':
|
||||
self.player.playlist.loop_off()
|
||||
embed = self.embeds.LOOP_DISABLE()
|
||||
return ControllerResponse(self.ctx, embed)
|
||||
else:
|
||||
error = BadCommandUsage()
|
||||
embed = self.embeds.BAD_LOOP_USE()
|
||||
return ControllerResponse(self.ctx, embed, error)
|
||||
@ -1,63 +0,0 @@
|
||||
from typing import Union
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client
|
||||
from Controllers.AbstractController import AbstractController
|
||||
from Controllers.ControllerResponse import ControllerResponse
|
||||
from Exceptions.Exceptions import BadCommandUsage, VulkanError, InvalidInput, NumberRequired, UnknownError
|
||||
from Music.Downloader import Downloader
|
||||
|
||||
|
||||
class MoveController(AbstractController):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
self.__down = Downloader()
|
||||
|
||||
async def run(self, pos1: str, pos2: str) -> ControllerResponse:
|
||||
if not self.player.playing:
|
||||
embed = self.embeds.NOT_PLAYING()
|
||||
error = BadCommandUsage()
|
||||
return ControllerResponse(self.ctx, embed, error)
|
||||
|
||||
error = self.__validate_input(pos1, pos2)
|
||||
if error:
|
||||
embed = self.embeds.ERROR_EMBED(error.message)
|
||||
return ControllerResponse(self.ctx, embed, error)
|
||||
|
||||
pos1, pos2 = self.__sanitize_input(pos1, pos2)
|
||||
playlist = self.player.playlist
|
||||
|
||||
if not playlist.validate_position(pos1) or not playlist.validate_position(pos2):
|
||||
error = InvalidInput()
|
||||
embed = self.embeds.PLAYLIST_RANGE_ERROR()
|
||||
return ControllerResponse(self.ctx, embed, error)
|
||||
try:
|
||||
song = self.player.playlist.move_songs(pos1, pos2)
|
||||
|
||||
songs = self.player.playlist.songs_to_preload
|
||||
await self.__down.preload(songs)
|
||||
|
||||
song_name = song.title if song.title else song.identifier
|
||||
embed = self.embeds.SONG_MOVED(song_name, pos1, pos2)
|
||||
return ControllerResponse(self.ctx, embed)
|
||||
except:
|
||||
embed = self.embeds.ERROR_MOVING()
|
||||
error = UnknownError()
|
||||
return ControllerResponse(self.ctx, embed, error)
|
||||
|
||||
def __validate_input(self, pos1: str, pos2: str) -> Union[VulkanError, None]:
|
||||
try:
|
||||
pos1 = int(pos1)
|
||||
pos2 = int(pos2)
|
||||
except:
|
||||
return NumberRequired(self.messages.ERROR_NUMBER)
|
||||
|
||||
def __sanitize_input(self, pos1: int, pos2: int) -> tuple:
|
||||
pos1 = int(pos1)
|
||||
pos2 = int(pos2)
|
||||
|
||||
if pos1 == -1:
|
||||
pos1 = len(self.player.playlist)
|
||||
if pos2 == -1:
|
||||
pos2 = len(self.player.playlist)
|
||||
|
||||
return pos1, pos2
|
||||
@ -1,26 +0,0 @@
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client
|
||||
from Controllers.AbstractController import AbstractController
|
||||
from Controllers.ControllerResponse import ControllerResponse
|
||||
from Utils.Cleaner import Cleaner
|
||||
|
||||
|
||||
class NowPlayingController(AbstractController):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
self.__cleaner = Cleaner()
|
||||
|
||||
async def run(self) -> ControllerResponse:
|
||||
if not self.player.playing:
|
||||
embed = self.embeds.NOT_PLAYING()
|
||||
return ControllerResponse(self.ctx, embed)
|
||||
|
||||
if self.player.playlist.looping_one:
|
||||
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.current.info
|
||||
embed = self.embeds.SONG_INFO(info, title)
|
||||
return ControllerResponse(self.ctx, embed)
|
||||
@ -1,16 +0,0 @@
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client
|
||||
from Controllers.AbstractController import AbstractController
|
||||
from Controllers.ControllerResponse import ControllerResponse
|
||||
|
||||
|
||||
class PauseController(AbstractController):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
|
||||
async def run(self) -> ControllerResponse:
|
||||
if self.guild.voice_client is not None:
|
||||
if self.guild.voice_client.is_playing():
|
||||
self.guild.voice_client.pause()
|
||||
|
||||
return ControllerResponse(self.ctx)
|
||||
@ -1,103 +0,0 @@
|
||||
import asyncio
|
||||
from Exceptions.Exceptions import DownloadingError, InvalidInput, VulkanError
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client
|
||||
from Controllers.AbstractController import AbstractController
|
||||
from Exceptions.Exceptions import ImpossibleMove, UnknownError
|
||||
from Controllers.ControllerResponse import ControllerResponse
|
||||
from Music.Downloader import Downloader
|
||||
from Music.Searcher import Searcher
|
||||
from Music.Song import Song
|
||||
|
||||
|
||||
class PlayController(AbstractController):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
self.__searcher = Searcher()
|
||||
self.__down = Downloader()
|
||||
|
||||
async def run(self, args: str) -> ControllerResponse:
|
||||
track = " ".join(args)
|
||||
requester = self.ctx.author.name
|
||||
|
||||
if not self.__user_connected():
|
||||
error = ImpossibleMove()
|
||||
embed = self.embeds.NO_CHANNEL()
|
||||
return ControllerResponse(self.ctx, embed, error)
|
||||
|
||||
if not self.__is_connected():
|
||||
success = await self.__connect()
|
||||
if not success:
|
||||
error = UnknownError()
|
||||
embed = self.embeds.UNKNOWN_ERROR()
|
||||
return ControllerResponse(self.ctx, embed, error)
|
||||
|
||||
try:
|
||||
musics = await self.__searcher.search(track)
|
||||
if musics is None or len(musics) == 0:
|
||||
raise InvalidInput(self.messages.INVALID_INPUT, self.messages.ERROR_TITLE)
|
||||
|
||||
for music in musics:
|
||||
song = Song(music, self.player.playlist, requester)
|
||||
self.player.playlist.add_song(song)
|
||||
quant = len(musics)
|
||||
|
||||
songs_preload = self.player.playlist.songs_to_preload
|
||||
await self.__down.preload(songs_preload)
|
||||
|
||||
if quant == 1:
|
||||
pos = len(self.player.playlist)
|
||||
song = self.__down.finish_one_song(song)
|
||||
if song.problematic:
|
||||
embed = self.embeds.SONG_PROBLEMATIC()
|
||||
error = DownloadingError()
|
||||
response = ControllerResponse(self.ctx, embed, error)
|
||||
|
||||
elif not self.player.playing:
|
||||
embed = self.embeds.SONG_ADDED(song.title)
|
||||
response = ControllerResponse(self.ctx, embed)
|
||||
else:
|
||||
embed = self.embeds.SONG_ADDED_TWO(song.info, pos)
|
||||
response = ControllerResponse(self.ctx, embed)
|
||||
else:
|
||||
embed = self.embeds.SONGS_ADDED(quant)
|
||||
response = ControllerResponse(self.ctx, embed)
|
||||
|
||||
asyncio.create_task(self.player.play(self.ctx))
|
||||
return response
|
||||
|
||||
except Exception as err:
|
||||
if isinstance(err, VulkanError): # If error was already processed
|
||||
print(f'DEVELOPER NOTE -> PlayController Error: {err.message}')
|
||||
error = err
|
||||
embed = self.embeds.CUSTOM_ERROR(error)
|
||||
else:
|
||||
error = UnknownError()
|
||||
embed = self.embeds.UNKNOWN_ERROR()
|
||||
|
||||
return ControllerResponse(self.ctx, embed, error)
|
||||
|
||||
def __user_connected(self) -> bool:
|
||||
if self.ctx.author.voice:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def __is_connected(self) -> bool:
|
||||
try:
|
||||
voice_channel = self.guild.voice_client.channel
|
||||
|
||||
if not self.guild.voice_client.is_connected():
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
async def __connect(self) -> bool:
|
||||
# if self.guild.voice_client is None:
|
||||
try:
|
||||
await self.ctx.author.voice.channel.connect(reconnect=True, timeout=None)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
@ -1,56 +0,0 @@
|
||||
from typing import Dict, List, Union
|
||||
from Config.Singleton import Singleton
|
||||
from discord import Guild, Client, VoiceClient, Member
|
||||
from Music.Player import Player
|
||||
|
||||
|
||||
class PlayersController(Singleton):
|
||||
def __init__(self, bot: Client = None) -> None:
|
||||
if not super().created:
|
||||
self.__bot: Client = bot
|
||||
if bot is not None:
|
||||
self.__players: Dict[Guild, Player] = self.__create_players()
|
||||
|
||||
def set_bot(self, bot: Client) -> None:
|
||||
self.__bot: Client = bot
|
||||
self.__players: Dict[Guild, Player] = self.__create_players()
|
||||
|
||||
def get_player(self, guild: Guild) -> Player:
|
||||
if guild not in self.__players.keys():
|
||||
player = Player(self.__bot, guild)
|
||||
self.__players[guild] = player
|
||||
|
||||
return self.__players[guild]
|
||||
|
||||
def reset_player(self, guild: Guild) -> None:
|
||||
if isinstance(guild, Guild):
|
||||
player = Player(self.__bot, guild)
|
||||
self.__players[guild] == player
|
||||
|
||||
def get_guild_voice(self, guild: Guild) -> Union[VoiceClient, None]:
|
||||
if guild.voice_client is None:
|
||||
return None
|
||||
else:
|
||||
return guild.voice_client
|
||||
|
||||
def create_player(self, guild: Guild) -> None:
|
||||
player = Player(self.__bot, guild)
|
||||
self.__players[guild] = player
|
||||
print(f'Player for guild {guild.name} created')
|
||||
|
||||
def __create_players(self) -> Dict[Guild, Player]:
|
||||
list_guilds: List[Guild] = self.__bot.guilds
|
||||
players: Dict[Guild, Player] = {}
|
||||
|
||||
for guild in list_guilds:
|
||||
player = Player(self.__bot, guild)
|
||||
players[guild] = player
|
||||
print(f'Player for guild {guild.name} created')
|
||||
|
||||
return players
|
||||
|
||||
def __get_guild_bot_member(self, guild: Guild) -> Member:
|
||||
members: List[Member] = guild.members
|
||||
for member in members:
|
||||
if member.id == self.__bot.user.id:
|
||||
return member
|
||||
@ -1,60 +0,0 @@
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client
|
||||
from Controllers.AbstractController import AbstractController
|
||||
from Exceptions.Exceptions import BadCommandUsage, ImpossibleMove, UnknownError
|
||||
from Controllers.ControllerResponse import ControllerResponse
|
||||
|
||||
|
||||
class PrevController(AbstractController):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
|
||||
async def run(self) -> ControllerResponse:
|
||||
if len(self.player.playlist.history()) == 0:
|
||||
error = ImpossibleMove()
|
||||
embed = self.embeds.NOT_PREVIOUS_SONG()
|
||||
return ControllerResponse(self.ctx, embed, error)
|
||||
|
||||
if not self.__user_connected():
|
||||
error = ImpossibleMove()
|
||||
embed = self.embeds.NO_CHANNEL()
|
||||
return ControllerResponse(self.ctx, embed, error)
|
||||
|
||||
if not self.__is_connected():
|
||||
success = await self.__connect()
|
||||
if not success:
|
||||
error = UnknownError()
|
||||
embed = self.embeds.UNKNOWN_ERROR()
|
||||
return ControllerResponse(self.ctx, embed, error)
|
||||
|
||||
if self.player.playlist.looping_all or self.player.playlist.looping_one:
|
||||
error = BadCommandUsage()
|
||||
embed = self.embeds.FAIL_DUE_TO_LOOP_ON()
|
||||
return ControllerResponse(self.ctx, embed, error)
|
||||
|
||||
await self.player.play_prev(self.ctx)
|
||||
|
||||
def __user_connected(self) -> bool:
|
||||
if self.ctx.author.voice:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def __is_connected(self) -> bool:
|
||||
try:
|
||||
voice_channel = self.guild.voice_client.channel
|
||||
|
||||
if not self.guild.voice_client.is_connected():
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
async def __connect(self) -> bool:
|
||||
# if self.guild.voice_client is None:
|
||||
try:
|
||||
await self.ctx.author.voice.channel.connect(reconnect=True, timeout=None)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
@ -1,44 +0,0 @@
|
||||
import asyncio
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client
|
||||
from Controllers.AbstractController import AbstractController
|
||||
from Controllers.ControllerResponse import ControllerResponse
|
||||
from Music.Downloader import Downloader
|
||||
from Utils.Utils import Utils
|
||||
|
||||
|
||||
class QueueController(AbstractController):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
self.__down = Downloader()
|
||||
|
||||
async def run(self) -> ControllerResponse:
|
||||
if self.player.playlist.looping_one:
|
||||
song = self.player.playlist.current
|
||||
embed = self.embeds.ONE_SONG_LOOPING(song.info)
|
||||
return ControllerResponse(self.ctx, embed)
|
||||
|
||||
songs_preload = self.player.playlist.songs_to_preload
|
||||
if len(songs_preload) == 0:
|
||||
embed = self.embeds.EMPTY_QUEUE()
|
||||
return ControllerResponse(self.ctx, embed)
|
||||
|
||||
asyncio.create_task(self.__down.preload(songs_preload))
|
||||
|
||||
if self.player.playlist.looping_all:
|
||||
title = self.messages.ALL_SONGS_LOOPING
|
||||
else:
|
||||
title = self.messages.QUEUE_TITLE
|
||||
|
||||
total_time = Utils.format_time(sum([int(song.duration if song.duration else 0)
|
||||
for song in songs_preload]))
|
||||
total_songs = len(self.player.playlist)
|
||||
|
||||
text = f'📜 Queue length: {total_songs} | ⌛ Duration: `{total_time}` downloaded \n\n'
|
||||
|
||||
for pos, song in enumerate(songs_preload, start=1):
|
||||
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)
|
||||
return ControllerResponse(self.ctx, embed)
|
||||
@ -1,52 +0,0 @@
|
||||
from typing import Union
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client
|
||||
from Controllers.AbstractController import AbstractController
|
||||
from Controllers.ControllerResponse import ControllerResponse
|
||||
from Exceptions.Exceptions import BadCommandUsage, VulkanError, ErrorRemoving, InvalidInput, NumberRequired
|
||||
|
||||
|
||||
class RemoveController(AbstractController):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
|
||||
async def run(self, position: str) -> ControllerResponse:
|
||||
if not self.player.playlist:
|
||||
embed = self.embeds.NOT_PLAYING()
|
||||
error = BadCommandUsage()
|
||||
return ControllerResponse(self.ctx, embed, error)
|
||||
|
||||
error = self.__validate_input(position)
|
||||
if error:
|
||||
embed = self.embeds.ERROR_EMBED(error.message)
|
||||
return ControllerResponse(self.ctx, embed, error)
|
||||
|
||||
position = self.__sanitize_input(position)
|
||||
if not self.player.playlist.validate_position(position):
|
||||
error = InvalidInput()
|
||||
embed = self.embeds.PLAYLIST_RANGE_ERROR()
|
||||
return ControllerResponse(self.ctx, embed, error)
|
||||
|
||||
try:
|
||||
song = self.player.playlist.remove_song(position)
|
||||
name = song.title if song.title else song.identifier
|
||||
|
||||
embed = self.embeds.SONG_REMOVED(name)
|
||||
return ControllerResponse(self.ctx, embed)
|
||||
except:
|
||||
error = ErrorRemoving()
|
||||
embed = self.embeds.ERROR_REMOVING()
|
||||
return ControllerResponse(self.ctx, embed, error)
|
||||
|
||||
def __validate_input(self, position: str) -> Union[VulkanError, None]:
|
||||
try:
|
||||
position = int(position)
|
||||
except:
|
||||
return NumberRequired(self.messages.ERROR_NUMBER)
|
||||
|
||||
def __sanitize_input(self, position: str) -> int:
|
||||
position = int(position)
|
||||
|
||||
if position == -1:
|
||||
position = len(self.player.playlist)
|
||||
return position
|
||||
@ -1,20 +0,0 @@
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client, Member
|
||||
from Controllers.AbstractController import AbstractController
|
||||
from Controllers.ControllerResponse import ControllerResponse
|
||||
from Controllers.PlayerController import PlayersController
|
||||
|
||||
|
||||
class ResetController(AbstractController):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
self.__controller = PlayersController(self.bot)
|
||||
|
||||
async def run(self) -> ControllerResponse:
|
||||
try:
|
||||
await self.player.force_stop()
|
||||
await self.bot_member.move_to(None)
|
||||
self.__controller.reset_player(self.guild)
|
||||
return ControllerResponse(self.ctx)
|
||||
except Exception as e:
|
||||
print(f'DEVELOPER NOTE -> Reset Error: {e}')
|
||||
@ -1,16 +0,0 @@
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client
|
||||
from Controllers.AbstractController import AbstractController
|
||||
from Controllers.ControllerResponse import ControllerResponse
|
||||
|
||||
|
||||
class ResumeController(AbstractController):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
|
||||
async def run(self) -> ControllerResponse:
|
||||
if self.guild.voice_client is not None:
|
||||
if self.guild.voice_client.is_paused():
|
||||
self.guild.voice_client.resume()
|
||||
|
||||
return ControllerResponse(self.ctx)
|
||||
@ -1,27 +0,0 @@
|
||||
import asyncio
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client
|
||||
from Controllers.AbstractController import AbstractController
|
||||
from Controllers.ControllerResponse import ControllerResponse
|
||||
from Exceptions.Exceptions import UnknownError
|
||||
from Music.Downloader import Downloader
|
||||
|
||||
|
||||
class ShuffleController(AbstractController):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
self.__down = Downloader()
|
||||
|
||||
async def run(self) -> ControllerResponse:
|
||||
try:
|
||||
self.player.playlist.shuffle()
|
||||
songs = self.player.playlist.songs_to_preload
|
||||
|
||||
asyncio.create_task(self.__down.preload(songs))
|
||||
embed = self.embeds.SONGS_SHUFFLED()
|
||||
return ControllerResponse(self.ctx, embed)
|
||||
except Exception as e:
|
||||
print(f'DEVELOPER NOTE -> Error Shuffling: {e}')
|
||||
error = UnknownError()
|
||||
embed = self.embeds.ERROR_SHUFFLING()
|
||||
return ControllerResponse(self.ctx, embed, error)
|
||||
@ -1,23 +0,0 @@
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client
|
||||
from Controllers.AbstractController import AbstractController
|
||||
from Exceptions.Exceptions import BadCommandUsage
|
||||
from Controllers.ControllerResponse import ControllerResponse
|
||||
|
||||
|
||||
class SkipController(AbstractController):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
|
||||
async def run(self) -> ControllerResponse:
|
||||
if self.player.playlist.looping_one:
|
||||
embed = self.embeds.ERROR_DUE_LOOP_ONE_ON()
|
||||
error = BadCommandUsage()
|
||||
return ControllerResponse(self.ctx, embed, error)
|
||||
|
||||
voice = self.controller.get_guild_voice(self.guild)
|
||||
if voice is None:
|
||||
return ControllerResponse(self.ctx)
|
||||
else:
|
||||
voice.stop()
|
||||
return ControllerResponse(self.ctx)
|
||||
@ -1,23 +0,0 @@
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client
|
||||
from Controllers.AbstractController import AbstractController
|
||||
from Controllers.ControllerResponse import ControllerResponse
|
||||
|
||||
|
||||
class StopController(AbstractController):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
|
||||
async def run(self) -> ControllerResponse:
|
||||
if self.guild.voice_client is None:
|
||||
return ControllerResponse(self.ctx)
|
||||
|
||||
if self.guild.voice_client.is_connected():
|
||||
self.player.playlist.clear()
|
||||
self.player.playlist.loop_off()
|
||||
self.guild.voice_client.stop()
|
||||
await self.guild.voice_client.disconnect()
|
||||
return ControllerResponse(self.ctx)
|
||||
|
||||
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
class Database:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
@ -1,5 +1,5 @@
|
||||
from discord import Client, Game, Status, Embed
|
||||
from discord.ext.commands.errors import CommandNotFound, MissingRequiredArgument, UserInputError
|
||||
from discord.ext.commands.errors import CommandNotFound, MissingRequiredArgument
|
||||
from discord.ext import commands
|
||||
from Config.Configs import Configs
|
||||
from Config.Helper import Helper
|
||||
@ -10,7 +10,8 @@ from Views.Embeds import Embeds
|
||||
helper = Helper()
|
||||
|
||||
|
||||
class Control(commands.Cog):
|
||||
class ControlCog(commands.Cog):
|
||||
"""Class to handle discord events"""
|
||||
|
||||
def __init__(self, bot: Client):
|
||||
self.__bot = bot
|
||||
@ -18,7 +19,7 @@ class Control(commands.Cog):
|
||||
self.__messages = Messages()
|
||||
self.__colors = Colors()
|
||||
self.__embeds = Embeds()
|
||||
self.__comandos = {
|
||||
self.__commands = {
|
||||
'MUSIC': ['resume', 'pause', 'loop', 'stop',
|
||||
'skip', 'play', 'queue', 'clear',
|
||||
'np', 'shuffle', 'move', 'remove',
|
||||
@ -44,7 +45,7 @@ class Control(commands.Cog):
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
else:
|
||||
print(f'DEVELOPER NOTE -> Comand Error: {error}')
|
||||
print(f'DEVELOPER NOTE -> Command Error: {error}')
|
||||
embed = self.__embeds.UNKNOWN_ERROR()
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@ -65,9 +66,9 @@ class Control(commands.Cog):
|
||||
return
|
||||
|
||||
embedhelp = Embed(
|
||||
title='Command Help',
|
||||
description=f'Command {command_help} Not Found',
|
||||
colour=self.__colors.RED
|
||||
title='Help',
|
||||
description=f'Command {command_help} do not exists, type {self.__config.BOT_PREFIX}help to see all commands',
|
||||
colour=self.__colors.BLACK
|
||||
)
|
||||
|
||||
await ctx.send(embed=embedhelp)
|
||||
@ -79,10 +80,10 @@ class Control(commands.Cog):
|
||||
help_help = '👾 `HELP`\n'
|
||||
|
||||
for command in self.__bot.commands:
|
||||
if command.name in self.__comandos['MUSIC']:
|
||||
if command.name in self.__commands['MUSIC']:
|
||||
help_music += f'**{command}** - {command.help}\n'
|
||||
|
||||
elif command.name in self.__comandos['RANDOM']:
|
||||
elif command.name in self.__commands['RANDOM']:
|
||||
help_random += f'**{command}** - {command.help}\n'
|
||||
|
||||
else:
|
||||
@ -99,10 +100,9 @@ class Control(commands.Cog):
|
||||
embedhelp.set_thumbnail(url=self.__bot.user.avatar_url)
|
||||
await ctx.send(embed=embedhelp)
|
||||
|
||||
@commands.command(name='invite', help=helper.HELP_INVITE, description=helper.HELP_INVITE_LONG)
|
||||
@commands.command(name='invite', help=helper.HELP_INVITE, description=helper.HELP_INVITE_LONG, aliases=['convite', 'inv', 'convidar'])
|
||||
async def invite_bot(self, ctx):
|
||||
invite_url = self.__config.INVITE_URL.format(self.__bot.user.id)
|
||||
print(invite_url)
|
||||
txt = self.__config.INVITE_MESSAGE.format(invite_url, invite_url)
|
||||
|
||||
embed = Embed(
|
||||
@ -115,4 +115,4 @@ class Control(commands.Cog):
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(Control(bot))
|
||||
bot.add_cog(ControlCog(bot))
|
||||
235
DiscordCogs/MusicCog.py
Normal file
235
DiscordCogs/MusicCog.py
Normal file
@ -0,0 +1,235 @@
|
||||
from discord import Guild, Client
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
from Config.Helper import Helper
|
||||
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 Views.EmoteView import EmoteView
|
||||
from Views.EmbedView import EmbedView
|
||||
|
||||
helper = Helper()
|
||||
|
||||
|
||||
class MusicCog(commands.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) -> None:
|
||||
self.__bot: Client = bot
|
||||
|
||||
@commands.command(name="play", help=helper.HELP_PLAY, description=helper.HELP_PLAY_LONG, aliases=['p', 'tocar'])
|
||||
async def play(self, ctx: Context, *args) -> None:
|
||||
try:
|
||||
controller = PlayHandler(ctx, self.__bot)
|
||||
|
||||
response = await controller.run(args)
|
||||
if response is not None:
|
||||
view1 = EmbedView(response)
|
||||
view2 = EmoteView(response)
|
||||
await view1.run()
|
||||
await view2.run()
|
||||
except Exception as e:
|
||||
print(f'[ERROR IN COG] -> {e}')
|
||||
|
||||
@commands.command(name="queue", help=helper.HELP_QUEUE, description=helper.HELP_QUEUE_LONG, aliases=['q', 'fila', 'musicas'])
|
||||
async def queue(self, ctx: Context) -> None:
|
||||
try:
|
||||
controller = QueueHandler(ctx, self.__bot)
|
||||
|
||||
response = await controller.run()
|
||||
view2 = EmbedView(response)
|
||||
await view2.run()
|
||||
except Exception as e:
|
||||
print(f'[ERROR IN COG] -> {e}')
|
||||
|
||||
@commands.command(name="skip", help=helper.HELP_SKIP, description=helper.HELP_SKIP_LONG, aliases=['s', 'pular', 'next'])
|
||||
async def skip(self, ctx: Context) -> None:
|
||||
try:
|
||||
controller = SkipHandler(ctx, self.__bot)
|
||||
|
||||
response = await controller.run()
|
||||
if response.success:
|
||||
view = EmoteView(response)
|
||||
else:
|
||||
view = EmbedView(response)
|
||||
|
||||
await view.run()
|
||||
except Exception as e:
|
||||
print(f'[ERROR IN COG] -> {e}')
|
||||
|
||||
@commands.command(name='stop', help=helper.HELP_STOP, description=helper.HELP_STOP_LONG, aliases=['parar'])
|
||||
async def stop(self, ctx: Context) -> None:
|
||||
try:
|
||||
controller = StopHandler(ctx, self.__bot)
|
||||
|
||||
response = await controller.run()
|
||||
if response.success:
|
||||
view = EmoteView(response)
|
||||
else:
|
||||
view = EmbedView(response)
|
||||
|
||||
await view.run()
|
||||
except Exception as e:
|
||||
print(f'[ERROR IN COG] -> {e}')
|
||||
|
||||
@commands.command(name='pause', help=helper.HELP_PAUSE, description=helper.HELP_PAUSE_LONG, aliases=['pausar', 'pare'])
|
||||
async def pause(self, ctx: Context) -> None:
|
||||
try:
|
||||
controller = PauseHandler(ctx, self.__bot)
|
||||
|
||||
response = await controller.run()
|
||||
view1 = EmoteView(response)
|
||||
view2 = EmbedView(response)
|
||||
await view1.run()
|
||||
await view2.run()
|
||||
except Exception as e:
|
||||
print(f'[ERROR IN COG] -> {e}')
|
||||
|
||||
@commands.command(name='resume', help=helper.HELP_RESUME, description=helper.HELP_RESUME_LONG, aliases=['soltar', 'despausar'])
|
||||
async def resume(self, ctx: Context) -> None:
|
||||
try:
|
||||
controller = ResumeHandler(ctx, self.__bot)
|
||||
|
||||
response = await controller.run()
|
||||
view1 = EmoteView(response)
|
||||
view2 = EmbedView(response)
|
||||
await view1.run()
|
||||
await view2.run()
|
||||
except Exception as e:
|
||||
print(f'[ERROR IN COG] -> {e}')
|
||||
|
||||
@commands.command(name='prev', help=helper.HELP_PREV, description=helper.HELP_PREV_LONG, aliases=['anterior', 'return', 'previous', 'back'])
|
||||
async def prev(self, ctx: Context) -> None:
|
||||
try:
|
||||
controller = PrevHandler(ctx, self.__bot)
|
||||
|
||||
response = await controller.run()
|
||||
if response is not None:
|
||||
view1 = EmbedView(response)
|
||||
view2 = EmoteView(response)
|
||||
await view1.run()
|
||||
await view2.run()
|
||||
except Exception as e:
|
||||
print(f'[ERROR IN COG] -> {e}')
|
||||
|
||||
@commands.command(name='history', help=helper.HELP_HISTORY, description=helper.HELP_HISTORY_LONG, aliases=['historico', 'anteriores', 'hist'])
|
||||
async def history(self, ctx: Context) -> None:
|
||||
try:
|
||||
controller = HistoryHandler(ctx, self.__bot)
|
||||
|
||||
response = await controller.run()
|
||||
view1 = EmbedView(response)
|
||||
view2 = EmoteView(response)
|
||||
await view1.run()
|
||||
await view2.run()
|
||||
except Exception as e:
|
||||
print(f'[ERROR IN COG] -> {e}')
|
||||
|
||||
@commands.command(name='loop', help=helper.HELP_LOOP, description=helper.HELP_LOOP_LONG, aliases=['l', 'repeat'])
|
||||
async def loop(self, ctx: Context, args='') -> None:
|
||||
try:
|
||||
controller = LoopHandler(ctx, self.__bot)
|
||||
|
||||
response = await controller.run(args)
|
||||
view1 = EmoteView(response)
|
||||
view2 = EmbedView(response)
|
||||
await view1.run()
|
||||
await view2.run()
|
||||
except Exception as e:
|
||||
print(f'[ERROR IN COG] -> {e}')
|
||||
|
||||
@commands.command(name='clear', help=helper.HELP_CLEAR, description=helper.HELP_CLEAR_LONG, aliases=['c', 'limpar'])
|
||||
async def clear(self, ctx: Context) -> None:
|
||||
try:
|
||||
controller = ClearHandler(ctx, self.__bot)
|
||||
|
||||
response = await controller.run()
|
||||
view = EmoteView(response)
|
||||
await view.run()
|
||||
except Exception as e:
|
||||
print(f'[ERROR IN COG] -> {e}')
|
||||
|
||||
@commands.command(name='np', help=helper.HELP_NP, description=helper.HELP_NP_LONG, aliases=['playing', 'now', 'this'])
|
||||
async def now_playing(self, ctx: Context) -> None:
|
||||
try:
|
||||
controller = NowPlayingHandler(ctx, self.__bot)
|
||||
|
||||
response = await controller.run()
|
||||
view1 = EmbedView(response)
|
||||
view2 = EmoteView(response)
|
||||
await view1.run()
|
||||
await view2.run()
|
||||
except Exception as e:
|
||||
print(f'[ERROR IN COG] -> {e}')
|
||||
|
||||
@commands.command(name='shuffle', help=helper.HELP_SHUFFLE, description=helper.HELP_SHUFFLE_LONG, aliases=['aleatorio', 'misturar'])
|
||||
async def shuffle(self, ctx: Context) -> None:
|
||||
try:
|
||||
controller = ShuffleHandler(ctx, self.__bot)
|
||||
|
||||
response = await controller.run()
|
||||
view1 = EmbedView(response)
|
||||
view2 = EmoteView(response)
|
||||
await view1.run()
|
||||
await view2.run()
|
||||
except Exception as e:
|
||||
print(f'[ERROR IN COG] -> {e}')
|
||||
|
||||
@commands.command(name='move', help=helper.HELP_MOVE, description=helper.HELP_MOVE_LONG, aliases=['m', 'mover'])
|
||||
async def move(self, ctx: Context, pos1, pos2='1') -> None:
|
||||
try:
|
||||
controller = MoveHandler(ctx, self.__bot)
|
||||
|
||||
response = await controller.run(pos1, pos2)
|
||||
view1 = EmbedView(response)
|
||||
view2 = EmoteView(response)
|
||||
await view1.run()
|
||||
await view2.run()
|
||||
except Exception as e:
|
||||
print(f'[ERROR IN COG] -> {e}')
|
||||
|
||||
@commands.command(name='remove', help=helper.HELP_REMOVE, description=helper.HELP_REMOVE_LONG, aliases=['remover'])
|
||||
async def remove(self, ctx: Context, position) -> None:
|
||||
try:
|
||||
controller = RemoveHandler(ctx, self.__bot)
|
||||
|
||||
response = await controller.run(position)
|
||||
view1 = EmbedView(response)
|
||||
view2 = EmoteView(response)
|
||||
await view1.run()
|
||||
await view2.run()
|
||||
except Exception as e:
|
||||
print(f'[ERROR IN COG] -> {e}')
|
||||
|
||||
@commands.command(name='reset', help=helper.HELP_RESET, description=helper.HELP_RESET_LONG, aliases=['resetar'])
|
||||
async def reset(self, ctx: Context) -> None:
|
||||
try:
|
||||
controller = ResetHandler(ctx, self.__bot)
|
||||
|
||||
response = await controller.run()
|
||||
view1 = EmbedView(response)
|
||||
view2 = EmoteView(response)
|
||||
await view1.run()
|
||||
await view2.run()
|
||||
except Exception as e:
|
||||
print(f'[ERROR IN COG] -> {e}')
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(MusicCog(bot))
|
||||
@ -1,22 +1,19 @@
|
||||
from random import randint, random
|
||||
from discord import Client
|
||||
from discord.ext.commands import Context, command, Cog
|
||||
from Config.Colors import Colors
|
||||
from Config.Configs import Configs
|
||||
from Config.Helper import Helper
|
||||
from Views.Embeds import Embeds
|
||||
|
||||
helper = Helper()
|
||||
|
||||
|
||||
class Random(Cog):
|
||||
class RandomCog(Cog):
|
||||
"""Class to listen to commands of type Random"""
|
||||
|
||||
def __init__(self, bot: Client):
|
||||
self.__config = Configs()
|
||||
self.__colors = Colors()
|
||||
self.__embeds = Embeds()
|
||||
|
||||
@command(name='random', help=helper.HELP_RANDOM, description=helper.HELP_RANDOM_LONG)
|
||||
@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)
|
||||
@ -37,7 +34,7 @@ class Random(Cog):
|
||||
embed = self.__embeds.RANDOM_NUMBER(a, b, x)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@command(name='cara', help=helper.HELP_CARA, description=helper.HELP_CARA_LONG)
|
||||
@command(name='cara', help=helper.HELP_CARA, description=helper.HELP_CARA_LONG, aliases=['coroa'])
|
||||
async def cara(self, ctx: Context) -> None:
|
||||
x = random()
|
||||
if x < 0.5:
|
||||
@ -48,7 +45,7 @@ class Random(Cog):
|
||||
embed = self.__embeds.CARA_COROA(result)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@command(name='choose', help=helper.HELP_CHOOSE, description=helper.HELP_CHOOSE_LONG)
|
||||
@command(name='choose', help=helper.HELP_CHOOSE, description=helper.HELP_CHOOSE_LONG, aliases=['escolha', 'pick'])
|
||||
async def choose(self, ctx, *args: str) -> None:
|
||||
try:
|
||||
user_input = " ".join(args)
|
||||
@ -64,4 +61,4 @@ class Random(Cog):
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(Random(bot))
|
||||
bot.add_cog(RandomCog(bot))
|
||||
@ -3,19 +3,15 @@ from typing import List
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client, Guild, ClientUser, Member
|
||||
from Config.Messages import Messages
|
||||
from Controllers.PlayerController import PlayersController
|
||||
from Music.Player import Player
|
||||
from Controllers.ControllerResponse import ControllerResponse
|
||||
from Handlers.HandlerResponse import HandlerResponse
|
||||
from Config.Configs import Configs
|
||||
from Config.Helper import Helper
|
||||
from Views.Embeds import Embeds
|
||||
|
||||
|
||||
class AbstractController(ABC):
|
||||
class AbstractHandler(ABC):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
self.__bot: Client = bot
|
||||
self.__controller = PlayersController(self.__bot)
|
||||
self.__player: Player = self.__controller.get_player(ctx.guild)
|
||||
self.__guild: Guild = ctx.guild
|
||||
self.__ctx: Context = ctx
|
||||
self.__bot_user: ClientUser = self.__bot.user
|
||||
@ -27,7 +23,7 @@ class AbstractController(ABC):
|
||||
self.__bot_member: Member = self.__get_member()
|
||||
|
||||
@abstractmethod
|
||||
async def run(self) -> ControllerResponse:
|
||||
async def run(self) -> HandlerResponse:
|
||||
pass
|
||||
|
||||
@property
|
||||
@ -46,14 +42,6 @@ class AbstractController(ABC):
|
||||
def guild(self) -> Guild:
|
||||
return self.__guild
|
||||
|
||||
@property
|
||||
def player(self) -> Player:
|
||||
return self.__player
|
||||
|
||||
@property
|
||||
def controller(self) -> PlayersController:
|
||||
return self.__controller
|
||||
|
||||
@property
|
||||
def bot(self) -> Client:
|
||||
return self.__bot
|
||||
29
Handlers/ClearHandler.py
Normal file
29
Handlers/ClearHandler.py
Normal file
@ -0,0 +1,29 @@
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client
|
||||
from Handlers.AbstractHandler import AbstractHandler
|
||||
from Handlers.HandlerResponse import HandlerResponse
|
||||
from Parallelism.ProcessManager import ProcessManager
|
||||
|
||||
|
||||
class ClearHandler(AbstractHandler):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
|
||||
async def run(self) -> HandlerResponse:
|
||||
# Get the current process of the guild
|
||||
processManager = ProcessManager()
|
||||
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
||||
if processInfo:
|
||||
# Clear the playlist
|
||||
playlist = processInfo.getPlaylist()
|
||||
processLock = processInfo.getLock()
|
||||
acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
|
||||
if acquired:
|
||||
playlist.clear()
|
||||
processLock.release()
|
||||
processLock.release()
|
||||
return HandlerResponse(self.ctx)
|
||||
else:
|
||||
processManager.resetProcess(self.guild, self.ctx)
|
||||
embed = self.embeds.PLAYER_RESTARTED()
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
@ -1,10 +1,10 @@
|
||||
from typing import Union
|
||||
from discord.ext.commands import Context
|
||||
from Exceptions.Exceptions import VulkanError
|
||||
from Config.Exceptions import VulkanError
|
||||
from discord import Embed
|
||||
|
||||
|
||||
class ControllerResponse:
|
||||
class HandlerResponse:
|
||||
def __init__(self, ctx: Context, embed: Embed = None, error: VulkanError = None) -> None:
|
||||
self.__ctx: Context = ctx
|
||||
self.__error: VulkanError = error
|
||||
40
Handlers/HistoryHandler.py
Normal file
40
Handlers/HistoryHandler.py
Normal file
@ -0,0 +1,40 @@
|
||||
from discord.ext.commands import Context
|
||||
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):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
|
||||
async def run(self) -> HandlerResponse:
|
||||
# Get the current process of the guild
|
||||
processManager = ProcessManager()
|
||||
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
||||
if processInfo:
|
||||
processLock = processInfo.getLock()
|
||||
acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
|
||||
if acquired:
|
||||
playlist = processInfo.getPlaylist()
|
||||
history = playlist.getSongsHistory()
|
||||
processLock.release()
|
||||
else:
|
||||
# If the player doesn't respond in time we restart it
|
||||
processManager.resetProcess(self.guild, self.ctx)
|
||||
embed = self.embeds.PLAYER_RESTARTED()
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
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):
|
||||
text += f"**`{pos}` - ** {song.title} - `{Utils.format_time(song.duration)}`\n"
|
||||
|
||||
embed = self.embeds.HISTORY(text)
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
58
Handlers/LoopHandler.py
Normal file
58
Handlers/LoopHandler.py
Normal file
@ -0,0 +1,58 @@
|
||||
from discord.ext.commands import Context
|
||||
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):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
|
||||
async def run(self, args: str) -> HandlerResponse:
|
||||
# Get the current process of the guild
|
||||
processManager = ProcessManager()
|
||||
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
||||
if not processInfo:
|
||||
embed = self.embeds.NOT_PLAYING()
|
||||
error = BadCommandUsage()
|
||||
return HandlerResponse(self.ctx, embed, error)
|
||||
|
||||
playlist = processInfo.getPlaylist()
|
||||
|
||||
processLock = processInfo.getLock()
|
||||
acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
|
||||
if acquired:
|
||||
if args == '' or args is None:
|
||||
playlist.loop_all()
|
||||
embed = self.embeds.LOOP_ALL_ACTIVATED()
|
||||
processLock.release()
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
|
||||
args = args.lower()
|
||||
error = None
|
||||
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()
|
||||
elif args == 'all':
|
||||
playlist.loop_all()
|
||||
embed = self.embeds.LOOP_ALL_ACTIVATED()
|
||||
elif args == 'off':
|
||||
playlist.loop_off()
|
||||
embed = self.embeds.LOOP_DISABLE()
|
||||
else:
|
||||
error = BadCommandUsage()
|
||||
embed = self.embeds.BAD_LOOP_USE()
|
||||
|
||||
processLock.release()
|
||||
return HandlerResponse(self.ctx, embed, error)
|
||||
else:
|
||||
processManager.resetProcess(self.guild, self.ctx)
|
||||
embed = self.embeds.PLAYER_RESTARTED()
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
74
Handlers/MoveHandler.py
Normal file
74
Handlers/MoveHandler.py
Normal file
@ -0,0 +1,74 @@
|
||||
from typing import Union
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client
|
||||
from Handlers.AbstractHandler import AbstractHandler
|
||||
from Handlers.HandlerResponse import HandlerResponse
|
||||
from Config.Exceptions import BadCommandUsage, VulkanError, InvalidInput, NumberRequired, UnknownError
|
||||
from Music.Playlist import Playlist
|
||||
from Parallelism.ProcessManager import ProcessManager
|
||||
|
||||
|
||||
class MoveHandler(AbstractHandler):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
|
||||
async def run(self, pos1: str, pos2: str) -> HandlerResponse:
|
||||
processManager = ProcessManager()
|
||||
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:
|
||||
error = self.__validateInput(pos1, pos2)
|
||||
if error:
|
||||
embed = self.embeds.ERROR_EMBED(error.message)
|
||||
processLock.release()
|
||||
return HandlerResponse(self.ctx, embed, error)
|
||||
|
||||
playlist = processInfo.getPlaylist()
|
||||
pos1, pos2 = self.__sanitizeInput(playlist, pos1, pos2)
|
||||
|
||||
if not playlist.validate_position(pos1) or not playlist.validate_position(pos2):
|
||||
error = InvalidInput()
|
||||
embed = self.embeds.PLAYLIST_RANGE_ERROR()
|
||||
processLock.release()
|
||||
return HandlerResponse(self.ctx, embed, error)
|
||||
try:
|
||||
song = playlist.move_songs(pos1, pos2)
|
||||
|
||||
song_name = song.title if song.title else song.identifier
|
||||
embed = self.embeds.SONG_MOVED(song_name, pos1, pos2)
|
||||
processLock.release()
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
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, pos1: str, pos2: str) -> Union[VulkanError, None]:
|
||||
try:
|
||||
pos1 = int(pos1)
|
||||
pos2 = int(pos2)
|
||||
except:
|
||||
return NumberRequired(self.messages.ERROR_NUMBER)
|
||||
|
||||
def __sanitizeInput(self, playlist: Playlist, pos1: int, pos2: int) -> tuple:
|
||||
pos1 = int(pos1)
|
||||
pos2 = int(pos2)
|
||||
|
||||
if pos1 == -1:
|
||||
pos1 = len(playlist.getSongs())
|
||||
if pos2 == -1:
|
||||
pos2 = len(playlist.getSongs())
|
||||
|
||||
return pos1, pos2
|
||||
35
Handlers/NowPlayingHandler.py
Normal file
35
Handlers/NowPlayingHandler.py
Normal file
@ -0,0 +1,35 @@
|
||||
from discord.ext.commands import Context
|
||||
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):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
self.__cleaner = Cleaner()
|
||||
|
||||
async def run(self) -> HandlerResponse:
|
||||
# Get the current process of the guild
|
||||
processManager = ProcessManager()
|
||||
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
||||
if not processInfo:
|
||||
embed = self.embeds.NOT_PLAYING()
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
|
||||
playlist = processInfo.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 = playlist.getCurrentSong().info
|
||||
embed = self.embeds.SONG_INFO(info, title)
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
25
Handlers/PauseHandler.py
Normal file
25
Handlers/PauseHandler.py
Normal file
@ -0,0 +1,25 @@
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client
|
||||
from Handlers.AbstractHandler import AbstractHandler
|
||||
from Handlers.HandlerResponse import HandlerResponse
|
||||
from Parallelism.ProcessManager import ProcessManager
|
||||
from Parallelism.Commands import VCommands, VCommandsType
|
||||
|
||||
|
||||
class PauseHandler(AbstractHandler):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
|
||||
async def run(self) -> HandlerResponse:
|
||||
processManager = ProcessManager()
|
||||
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
||||
if processInfo:
|
||||
# Send Pause command to be execute by player process
|
||||
command = VCommands(VCommandsType.PAUSE, None)
|
||||
queue = processInfo.getQueue()
|
||||
queue.put(command)
|
||||
|
||||
return HandlerResponse(self.ctx)
|
||||
else:
|
||||
embed = self.embeds.NOT_PLAYING()
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
135
Handlers/PlayHandler.py
Normal file
135
Handlers/PlayHandler.py
Normal file
@ -0,0 +1,135 @@
|
||||
import asyncio
|
||||
from typing import List
|
||||
from Config.Exceptions import DownloadingError, InvalidInput, VulkanError
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client
|
||||
from Handlers.AbstractHandler import AbstractHandler
|
||||
from Config.Exceptions import ImpossibleMove, UnknownError
|
||||
from Handlers.HandlerResponse import HandlerResponse
|
||||
from Music.Downloader import Downloader
|
||||
from Music.Searcher import Searcher
|
||||
from Music.Song import Song
|
||||
from Parallelism.ProcessManager import ProcessManager
|
||||
from Parallelism.ProcessInfo import ProcessInfo
|
||||
from Parallelism.Commands import VCommands, VCommandsType
|
||||
|
||||
|
||||
class PlayHandler(AbstractHandler):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
self.__searcher = Searcher()
|
||||
self.__down = Downloader()
|
||||
|
||||
async def run(self, args: str) -> HandlerResponse:
|
||||
track = " ".join(args)
|
||||
requester = self.ctx.author.name
|
||||
|
||||
if not self.__isUserConnected():
|
||||
error = ImpossibleMove()
|
||||
embed = self.embeds.NO_CHANNEL()
|
||||
return HandlerResponse(self.ctx, embed, error)
|
||||
|
||||
try:
|
||||
# Search for musics and get the name of each song
|
||||
musicsInfo = await self.__searcher.search(track)
|
||||
if musicsInfo is None or len(musicsInfo) == 0:
|
||||
raise InvalidInput(self.messages.INVALID_INPUT, self.messages.ERROR_TITLE)
|
||||
|
||||
# Get the process context for the current guild
|
||||
processManager = ProcessManager()
|
||||
processInfo = processManager.getPlayerInfo(self.guild, self.ctx)
|
||||
playlist = processInfo.getPlaylist()
|
||||
process = processInfo.getProcess()
|
||||
if not process.is_alive(): # If process has not yet started, start
|
||||
process.start()
|
||||
|
||||
# Create the Songs objects
|
||||
songs: List[Song] = []
|
||||
for musicInfo in musicsInfo:
|
||||
songs.append(Song(musicInfo, playlist, requester))
|
||||
|
||||
if len(songs) == 1:
|
||||
# If only one music, download it directly
|
||||
song = self.__down.finish_one_song(songs[0])
|
||||
if song.problematic: # If error in download song return
|
||||
embed = self.embeds.SONG_PROBLEMATIC()
|
||||
error = DownloadingError()
|
||||
return HandlerResponse(self.ctx, embed, error)
|
||||
|
||||
# If not playing
|
||||
if not playlist.getCurrentSong():
|
||||
embed = self.embeds.SONG_ADDED(song.title)
|
||||
response = HandlerResponse(self.ctx, embed)
|
||||
else: # If already playing
|
||||
pos = len(playlist.getSongs())
|
||||
embed = self.embeds.SONG_ADDED_TWO(song.info, pos)
|
||||
response = HandlerResponse(self.ctx, embed)
|
||||
|
||||
# Add the unique song to the playlist and send a command to player process
|
||||
processLock = processInfo.getLock()
|
||||
acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
|
||||
if acquired:
|
||||
playlist.add_song(song)
|
||||
# Release the acquired Lock
|
||||
processLock.release()
|
||||
queue = processInfo.getQueue()
|
||||
playCommand = VCommands(VCommandsType.PLAY, None)
|
||||
queue.put(playCommand)
|
||||
else:
|
||||
processManager.resetProcess(self.guild, self.ctx)
|
||||
embed = self.embeds.PLAYER_RESTARTED()
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
|
||||
return response
|
||||
else: # If multiple songs added
|
||||
# Trigger a task to download all songs and then store them in the process playlist
|
||||
asyncio.create_task(self.__downloadSongsAndStore(songs, processInfo))
|
||||
|
||||
embed = self.embeds.SONGS_ADDED(len(songs))
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
|
||||
except DownloadingError as error:
|
||||
embed = self.embeds.DOWNLOADING_ERROR()
|
||||
return HandlerResponse(self.ctx, embed, error)
|
||||
except Exception as error:
|
||||
if isinstance(error, VulkanError): # If error was already processed
|
||||
print(f'DEVELOPER NOTE -s> PlayController Error: {error.message}', {type(error)})
|
||||
embed = self.embeds.CUSTOM_ERROR(error)
|
||||
else:
|
||||
print(f'DEVELOPER NOTE -> PlayController Error: {error}, {type(error)}')
|
||||
error = UnknownError()
|
||||
embed = self.embeds.UNKNOWN_ERROR()
|
||||
|
||||
return HandlerResponse(self.ctx, embed, error)
|
||||
|
||||
async def __downloadSongsAndStore(self, songs: List[Song], processInfo: ProcessInfo) -> None:
|
||||
playlist = processInfo.getPlaylist()
|
||||
queue = processInfo.getQueue()
|
||||
playCommand = VCommands(VCommandsType.PLAY, None)
|
||||
# Trigger a task for each song to be downloaded
|
||||
tasks: List[asyncio.Task] = []
|
||||
for song in songs:
|
||||
task = asyncio.create_task(self.__down.download_song(song))
|
||||
tasks.append(task)
|
||||
|
||||
# In the original order, await for the task and then if successfully downloaded add in the playlist
|
||||
processManager = ProcessManager()
|
||||
for index, task in enumerate(tasks):
|
||||
await task
|
||||
song = songs[index]
|
||||
if not song.problematic: # If downloaded add to the playlist and send play command
|
||||
processInfo = processManager.getPlayerInfo(self.guild, self.ctx)
|
||||
processLock = processInfo.getLock()
|
||||
acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
|
||||
if acquired:
|
||||
playlist.add_song(song)
|
||||
queue.put(playCommand)
|
||||
processLock.release()
|
||||
else:
|
||||
processManager.resetProcess(self.guild, self.ctx)
|
||||
|
||||
def __isUserConnected(self) -> bool:
|
||||
if self.ctx.author.voice:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
53
Handlers/PrevHandler.py
Normal file
53
Handlers/PrevHandler.py
Normal file
@ -0,0 +1,53 @@
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client
|
||||
from Handlers.AbstractHandler import AbstractHandler
|
||||
from Config.Exceptions import BadCommandUsage, ImpossibleMove
|
||||
from Handlers.HandlerResponse import HandlerResponse
|
||||
from Parallelism.ProcessManager import ProcessManager
|
||||
from Parallelism.Commands import VCommands, VCommandsType
|
||||
|
||||
|
||||
class PrevHandler(AbstractHandler):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
|
||||
async def run(self) -> HandlerResponse:
|
||||
processManager = ProcessManager()
|
||||
processInfo = processManager.getPlayerInfo(self.guild, self.ctx)
|
||||
if not processInfo:
|
||||
embed = self.embeds.NOT_PLAYING()
|
||||
error = BadCommandUsage()
|
||||
return HandlerResponse(self.ctx, embed, error)
|
||||
|
||||
playlist = processInfo.getPlaylist()
|
||||
if len(playlist.getHistory()) == 0:
|
||||
error = ImpossibleMove()
|
||||
embed = self.embeds.NOT_PREVIOUS_SONG()
|
||||
return HandlerResponse(self.ctx, embed, error)
|
||||
|
||||
if not self.__user_connected():
|
||||
error = ImpossibleMove()
|
||||
embed = self.embeds.NO_CHANNEL()
|
||||
return HandlerResponse(self.ctx, embed, error)
|
||||
|
||||
if playlist.isLoopingAll() or playlist.isLoopingOne():
|
||||
error = BadCommandUsage()
|
||||
embed = self.embeds.FAIL_DUE_TO_LOOP_ON()
|
||||
return HandlerResponse(self.ctx, embed, error)
|
||||
|
||||
# If not started, start the player process
|
||||
process = processInfo.getProcess()
|
||||
if not process.is_alive():
|
||||
process.start()
|
||||
|
||||
# Send a prev command, together with the user voice channel
|
||||
prevCommand = VCommands(VCommandsType.PREV, self.ctx.author.voice.channel.id)
|
||||
queue = processInfo.getQueue()
|
||||
queue.put(prevCommand)
|
||||
return HandlerResponse(self.ctx)
|
||||
|
||||
def __user_connected(self) -> bool:
|
||||
if self.ctx.author.voice:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
64
Handlers/QueueHandler.py
Normal file
64
Handlers/QueueHandler.py
Normal file
@ -0,0 +1,64 @@
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client
|
||||
from Handlers.AbstractHandler import AbstractHandler
|
||||
from Handlers.HandlerResponse import HandlerResponse
|
||||
from Music.Downloader import Downloader
|
||||
from Utils.Utils import Utils
|
||||
from Parallelism.ProcessManager import ProcessManager
|
||||
|
||||
|
||||
class QueueHandler(AbstractHandler):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
self.__down = Downloader()
|
||||
|
||||
async def run(self) -> HandlerResponse:
|
||||
# Retrieve the process of the guild
|
||||
processManager = ProcessManager()
|
||||
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
||||
if not processInfo: # If no process return empty list
|
||||
embed = self.embeds.EMPTY_QUEUE()
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
|
||||
# Acquire the Lock to manipulate the playlist
|
||||
processLock = processInfo.getLock()
|
||||
acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
|
||||
if acquired:
|
||||
playlist = processInfo.getPlaylist()
|
||||
|
||||
if playlist.isLoopingOne():
|
||||
song = playlist.getCurrentSong()
|
||||
embed = self.embeds.ONE_SONG_LOOPING(song.info)
|
||||
processLock.release() # Release the Lock
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
|
||||
songs_preload = playlist.getSongsToPreload()
|
||||
allSongs = playlist.getSongs()
|
||||
if len(songs_preload) == 0:
|
||||
embed = self.embeds.EMPTY_QUEUE()
|
||||
processLock.release() # Release the Lock
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
|
||||
if playlist.isLoopingAll():
|
||||
title = self.messages.ALL_SONGS_LOOPING
|
||||
else:
|
||||
title = self.messages.QUEUE_TITLE
|
||||
|
||||
total_time = Utils.format_time(sum([int(song.duration if song.duration else 0)
|
||||
for song in allSongs]))
|
||||
total_songs = len(playlist.getSongs())
|
||||
|
||||
text = f'📜 Queue length: {total_songs} | ⌛ Duration: `{total_time}` downloaded \n\n'
|
||||
|
||||
for pos, song in enumerate(songs_preload, start=1):
|
||||
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)
|
||||
else:
|
||||
processManager.resetProcess(self.guild, self.ctx)
|
||||
embed = self.embeds.PLAYER_RESTARTED()
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
64
Handlers/RemoveHandler.py
Normal file
64
Handlers/RemoveHandler.py
Normal file
@ -0,0 +1,64 @@
|
||||
from typing import Union
|
||||
from discord.ext.commands import Context
|
||||
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):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
|
||||
async def run(self, position: str) -> HandlerResponse:
|
||||
# Get the current process of the guild
|
||||
processManager = ProcessManager()
|
||||
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:
|
||||
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.__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 = playlist.remove_song(position)
|
||||
name = song.title if song.title else song.identifier
|
||||
|
||||
embed = self.embeds.SONG_REMOVED(name)
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
except:
|
||||
error = ErrorRemoving()
|
||||
embed = self.embeds.ERROR_REMOVING()
|
||||
return HandlerResponse(self.ctx, embed, error)
|
||||
|
||||
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: str) -> int:
|
||||
position = int(position)
|
||||
|
||||
if position == -1:
|
||||
position = len(playlist.getSongs())
|
||||
return position
|
||||
25
Handlers/ResetHandler.py
Normal file
25
Handlers/ResetHandler.py
Normal file
@ -0,0 +1,25 @@
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client
|
||||
from Handlers.AbstractHandler import AbstractHandler
|
||||
from Handlers.HandlerResponse import HandlerResponse
|
||||
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)
|
||||
|
||||
async def run(self) -> HandlerResponse:
|
||||
# Get the current process of the guild
|
||||
processManager = ProcessManager()
|
||||
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
||||
if processInfo:
|
||||
command = VCommands(VCommandsType.RESET, None)
|
||||
queue = processInfo.getQueue()
|
||||
queue.put(command)
|
||||
|
||||
return HandlerResponse(self.ctx)
|
||||
else:
|
||||
embed = self.embeds.NOT_PLAYING()
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
25
Handlers/ResumeHandler.py
Normal file
25
Handlers/ResumeHandler.py
Normal file
@ -0,0 +1,25 @@
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client
|
||||
from Handlers.AbstractHandler import AbstractHandler
|
||||
from Handlers.HandlerResponse import HandlerResponse
|
||||
from Parallelism.ProcessManager import ProcessManager
|
||||
from Parallelism.Commands import VCommands, VCommandsType
|
||||
|
||||
|
||||
class ResumeHandler(AbstractHandler):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
|
||||
async def run(self) -> HandlerResponse:
|
||||
processManager = ProcessManager()
|
||||
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
||||
if processInfo:
|
||||
# Send Resume command to be execute by player process
|
||||
command = VCommands(VCommandsType.RESUME, None)
|
||||
queue = processInfo.getQueue()
|
||||
queue.put(command)
|
||||
|
||||
return HandlerResponse(self.ctx)
|
||||
else:
|
||||
embed = self.embeds.NOT_PLAYING()
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
41
Handlers/ShuffleHandler.py
Normal file
41
Handlers/ShuffleHandler.py
Normal file
@ -0,0 +1,41 @@
|
||||
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 Parallelism.ProcessManager import ProcessManager
|
||||
|
||||
|
||||
class ShuffleHandler(AbstractHandler):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
|
||||
async def run(self) -> HandlerResponse:
|
||||
processManager = ProcessManager()
|
||||
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
||||
if processInfo:
|
||||
try:
|
||||
processLock = processInfo.getLock()
|
||||
acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
|
||||
if acquired:
|
||||
playlist = processInfo.getPlaylist()
|
||||
playlist.shuffle()
|
||||
# Release the acquired Lock
|
||||
processLock.release()
|
||||
else:
|
||||
processManager.resetProcess(self.guild, self.ctx)
|
||||
embed = self.embeds.PLAYER_RESTARTED()
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
|
||||
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)
|
||||
32
Handlers/SkipHandler.py
Normal file
32
Handlers/SkipHandler.py
Normal file
@ -0,0 +1,32 @@
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client
|
||||
from Handlers.AbstractHandler import AbstractHandler
|
||||
from Config.Exceptions import BadCommandUsage
|
||||
from Handlers.HandlerResponse import HandlerResponse
|
||||
from Parallelism.ProcessManager import ProcessManager
|
||||
from Parallelism.Commands import VCommands, VCommandsType
|
||||
|
||||
|
||||
class SkipHandler(AbstractHandler):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
|
||||
async def run(self) -> HandlerResponse:
|
||||
processManager = ProcessManager()
|
||||
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
||||
if processInfo: # Verify if there is a running process
|
||||
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.getQueue()
|
||||
queue.put(command)
|
||||
|
||||
return HandlerResponse(self.ctx)
|
||||
else:
|
||||
embed = self.embeds.NOT_PLAYING()
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
25
Handlers/StopHandler.py
Normal file
25
Handlers/StopHandler.py
Normal file
@ -0,0 +1,25 @@
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client
|
||||
from Handlers.AbstractHandler import AbstractHandler
|
||||
from Handlers.HandlerResponse import HandlerResponse
|
||||
from Parallelism.ProcessManager import ProcessManager
|
||||
from Parallelism.Commands import VCommands, VCommandsType
|
||||
|
||||
|
||||
class StopHandler(AbstractHandler):
|
||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
||||
super().__init__(ctx, bot)
|
||||
|
||||
async def run(self) -> HandlerResponse:
|
||||
processManager = ProcessManager()
|
||||
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
||||
if processInfo:
|
||||
# Send command to player process stop
|
||||
command = VCommands(VCommandsType.STOP, None)
|
||||
queue = processInfo.getQueue()
|
||||
queue.put(command)
|
||||
|
||||
return HandlerResponse(self.ctx)
|
||||
else:
|
||||
embed = self.embeds.NOT_PLAYING()
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
@ -1,5 +1,5 @@
|
||||
import deezer
|
||||
from Exceptions.Exceptions import DeezerError
|
||||
from Config.Exceptions import DeezerError
|
||||
from Config.Messages import DeezerMessages
|
||||
|
||||
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
import asyncio
|
||||
from typing import List
|
||||
from Config.Configs import Configs
|
||||
from yt_dlp import YoutubeDL
|
||||
from yt_dlp import YoutubeDL, DownloadError
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from Music.Song import Song
|
||||
from Utils.Utils import Utils, run_async
|
||||
from Config.Exceptions import DownloadingError
|
||||
|
||||
|
||||
class Downloader():
|
||||
class Downloader:
|
||||
config = Configs()
|
||||
__YDL_OPTIONS = {'format': 'bestaudio/best',
|
||||
'default_search': 'auto',
|
||||
@ -40,20 +41,20 @@ class Downloader():
|
||||
self.__playlist_keys = ['entries']
|
||||
|
||||
def finish_one_song(self, song: Song) -> Song:
|
||||
if song.identifier is None:
|
||||
return None
|
||||
try:
|
||||
if song.identifier is None:
|
||||
return None
|
||||
|
||||
if Utils.is_url(song.identifier):
|
||||
song_info = self.__download_url(song.identifier)
|
||||
else:
|
||||
song_info = self.__download_title(song.identifier)
|
||||
if Utils.is_url(song.identifier):
|
||||
song_info = self.__download_url(song.identifier)
|
||||
else:
|
||||
song_info = self.__download_title(song.identifier)
|
||||
|
||||
song.finish_down(song_info)
|
||||
return song
|
||||
|
||||
async def preload(self, songs: List[Song]) -> None:
|
||||
for song in songs:
|
||||
asyncio.ensure_future(self.download_song(song))
|
||||
song.finish_down(song_info)
|
||||
return song
|
||||
# Convert yt_dlp error to my own error
|
||||
except DownloadError:
|
||||
raise DownloadingError()
|
||||
|
||||
@run_async
|
||||
def extract_info(self, url: str) -> List[dict]:
|
||||
@ -81,8 +82,11 @@ class Downloader():
|
||||
else: # Failed to extract the songs
|
||||
print(f'DEVELOPER NOTE -> Failed to Extract URL {url}')
|
||||
return []
|
||||
# Convert the yt_dlp download error to own error
|
||||
except DownloadError:
|
||||
raise DownloadingError()
|
||||
except Exception as e:
|
||||
print(f'DEVELOPER NOTE -> Error Extracting Music: {e}')
|
||||
print(f'DEVELOPER NOTE -> Error Extracting Music: {e}, {type(e)}')
|
||||
raise e
|
||||
else:
|
||||
return []
|
||||
|
||||
116
Music/Player.py
116
Music/Player.py
@ -1,116 +0,0 @@
|
||||
import asyncio
|
||||
from discord.ext import commands
|
||||
from discord import Client, Guild, FFmpegPCMAudio
|
||||
from discord.ext.commands import Context
|
||||
from Music.Downloader import Downloader
|
||||
from Music.Playlist import Playlist
|
||||
from Music.Song import Song
|
||||
from Utils.Utils import Timer
|
||||
|
||||
|
||||
class Player(commands.Cog):
|
||||
def __init__(self, bot: Client, guild: Guild):
|
||||
self.__down: Downloader = Downloader()
|
||||
self.__playlist: Playlist = Playlist()
|
||||
self.__bot: Client = bot
|
||||
self.__guild: Guild = guild
|
||||
|
||||
self.__timer = Timer(self.__timeout_handler)
|
||||
self.__playing = False
|
||||
|
||||
# Flag to control if the player should stop totally the playing
|
||||
self.__force_stop = False
|
||||
self.FFMPEG_OPTIONS = {'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5',
|
||||
'options': '-vn'}
|
||||
|
||||
@property
|
||||
def playing(self) -> bool:
|
||||
return self.__playing
|
||||
|
||||
@property
|
||||
def playlist(self) -> Playlist:
|
||||
return self.__playlist
|
||||
|
||||
async def play(self, ctx: Context) -> str:
|
||||
if not self.__playing:
|
||||
first_song = self.__playlist.next_song()
|
||||
await self.__play_music(ctx, first_song)
|
||||
|
||||
async def play_prev(self, ctx: Context) -> None:
|
||||
song = self.__playlist.prev_song()
|
||||
if song is not None:
|
||||
if self.__guild.voice_client.is_playing() or self.__guild.voice_client.is_paused():
|
||||
# Will forbidden next_song to execute after stopping current player
|
||||
self.__force_stop = True
|
||||
self.__guild.voice_client.stop()
|
||||
self.__playing = False
|
||||
|
||||
await self.__play_music(ctx, song)
|
||||
|
||||
async def force_stop(self) -> None:
|
||||
try:
|
||||
if self.__guild.voice_client is None:
|
||||
return
|
||||
|
||||
self.__guild.voice_client.stop()
|
||||
await self.__guild.voice_client.disconnect()
|
||||
self.__playlist.clear()
|
||||
self.__playlist.loop_off()
|
||||
except Exception as e:
|
||||
print(f'DEVELOPER NOTE -> Force Stop Error: {e}')
|
||||
|
||||
def __play_next(self, error, ctx: Context) -> None:
|
||||
if self.__force_stop: # If it's forced to stop player
|
||||
self.__force_stop = False
|
||||
return None
|
||||
|
||||
song = self.__playlist.next_song()
|
||||
|
||||
if song is not None:
|
||||
coro = self.__play_music(ctx, song)
|
||||
self.__bot.loop.create_task(coro)
|
||||
else:
|
||||
self.__playing = False
|
||||
|
||||
async def __play_music(self, ctx: Context, song: Song) -> None:
|
||||
try:
|
||||
source = await self.__ensure_source(song)
|
||||
if source is None:
|
||||
self.__play_next(None, ctx)
|
||||
|
||||
self.__playing = True
|
||||
|
||||
player = FFmpegPCMAudio(song.source, **self.FFMPEG_OPTIONS)
|
||||
voice = self.__guild.voice_client
|
||||
voice.play(player, after=lambda e: self.__play_next(e, ctx))
|
||||
|
||||
self.__timer.cancel()
|
||||
self.__timer = Timer(self.__timeout_handler)
|
||||
|
||||
await ctx.invoke(self.__bot.get_command('np'))
|
||||
|
||||
songs = self.__playlist.songs_to_preload
|
||||
asyncio.create_task(self.__down.preload(songs))
|
||||
except:
|
||||
self.__play_next(None, ctx)
|
||||
|
||||
async def __timeout_handler(self) -> None:
|
||||
if self.__guild.voice_client is None:
|
||||
return
|
||||
|
||||
if self.__guild.voice_client.is_playing() or self.__guild.voice_client.is_paused():
|
||||
self.__timer = Timer(self.__timeout_handler)
|
||||
|
||||
elif self.__guild.voice_client.is_connected():
|
||||
self.__playlist.clear()
|
||||
self.__playlist.loop_off()
|
||||
await self.__guild.voice_client.disconnect()
|
||||
|
||||
async def __ensure_source(self, song: Song) -> str:
|
||||
while True:
|
||||
await asyncio.sleep(0.1)
|
||||
if song.source is not None: # If song got downloaded
|
||||
return song.source
|
||||
|
||||
if song.problematic: # If song got any error
|
||||
return None
|
||||
@ -8,7 +8,7 @@ import random
|
||||
class Playlist:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__config = Configs()
|
||||
self.__configs = Configs()
|
||||
self.__queue = deque() # Store the musics to play
|
||||
self.__songs_history = deque() # Store the musics played
|
||||
|
||||
@ -17,6 +17,9 @@ class Playlist:
|
||||
|
||||
self.__current: Song = None
|
||||
|
||||
def getSongs(self) -> deque[Song]:
|
||||
return self.__queue
|
||||
|
||||
def validate_position(self, position: int) -> bool:
|
||||
if position not in range(1, len(self.__queue) + 1):
|
||||
return False
|
||||
@ -29,25 +32,23 @@ class Playlist:
|
||||
return False
|
||||
return True
|
||||
|
||||
@property
|
||||
def songs_history(self) -> deque:
|
||||
def getSongsHistory(self) -> deque:
|
||||
return self.__songs_history
|
||||
|
||||
@property
|
||||
def looping_one(self) -> bool:
|
||||
def isLoopingOne(self) -> bool:
|
||||
return self.__looping_one
|
||||
|
||||
@property
|
||||
def looping_all(self) -> bool:
|
||||
def isLoopingAll(self) -> bool:
|
||||
return self.__looping_all
|
||||
|
||||
@property
|
||||
def current(self) -> Song:
|
||||
def getCurrentSong(self) -> Song:
|
||||
return self.__current
|
||||
|
||||
@property
|
||||
def songs_to_preload(self) -> List[Song]:
|
||||
return list(self.__queue)[:self.__config.MAX_PRELOAD_SONGS]
|
||||
def setCurrentSong(self, song: Song) -> Song:
|
||||
self.__current = song
|
||||
|
||||
def getSongsToPreload(self) -> List[Song]:
|
||||
return list(self.__queue)[:self.__configs.MAX_PRELOAD_SONGS]
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.__queue)
|
||||
@ -64,7 +65,7 @@ class Playlist:
|
||||
if played_song.problematic == False:
|
||||
self.__songs_history.appendleft(played_song)
|
||||
|
||||
if len(self.__songs_history) > self.__config.MAX_SONGS_HISTORY:
|
||||
if len(self.__songs_history) > self.__configs.MAX_SONGS_HISTORY:
|
||||
self.__songs_history.pop() # Remove the older
|
||||
|
||||
elif self.__looping_one: # Insert the current song to play again
|
||||
@ -75,6 +76,7 @@ class Playlist:
|
||||
|
||||
# Get the new song
|
||||
if len(self.__queue) == 0:
|
||||
self.__current = None
|
||||
return None
|
||||
|
||||
self.__current = self.__queue.popleft()
|
||||
@ -135,7 +137,7 @@ class Playlist:
|
||||
|
||||
return song
|
||||
|
||||
def history(self) -> list:
|
||||
def getHistory(self) -> list:
|
||||
titles = []
|
||||
for song in self.__songs_history:
|
||||
title = song.title if song.title else 'Unknown'
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
from Exceptions.Exceptions import DeezerError, InvalidInput, SpotifyError, YoutubeError
|
||||
from Config.Exceptions import DeezerError, InvalidInput, SpotifyError, VulkanError, YoutubeError
|
||||
from Music.Downloader import Downloader
|
||||
from Music.Types import Provider
|
||||
from Music.Spotify import SpotifySearch
|
||||
from Music.SpotifySearcher import SpotifySearch
|
||||
from Music.DeezerSearcher import DeezerSearcher
|
||||
from Utils.Utils import Utils
|
||||
from Utils.UrlAnalyzer import URLAnalyzer
|
||||
@ -25,7 +25,10 @@ class Searcher():
|
||||
track = self.__cleanYoutubeInput(track)
|
||||
musics = await self.__down.extract_info(track)
|
||||
return musics
|
||||
except:
|
||||
except VulkanError as error:
|
||||
raise error
|
||||
except Exception as error:
|
||||
print(f'[Error in Searcher] -> {error}, {type(error)}')
|
||||
raise YoutubeError(self.__messages.YOUTUBE_NOT_FOUND, self.__messages.GENERIC_TITLE)
|
||||
|
||||
elif provider == Provider.Spotify:
|
||||
|
||||
@ -23,6 +23,7 @@ class Song:
|
||||
else:
|
||||
print(f'DEVELOPER NOTE -> {key} not found in info of music: {self.identifier}')
|
||||
self.destroy()
|
||||
return
|
||||
|
||||
for key in self.__useful_keys:
|
||||
if key in info.keys():
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
from spotipy import Spotify
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
from spotipy.exceptions import SpotifyException
|
||||
from Exceptions.Exceptions import SpotifyError
|
||||
from Config.Exceptions import SpotifyError
|
||||
from Config.Configs import Configs
|
||||
from Config.Messages import SpotifyMessages
|
||||
|
||||
25
Parallelism/Commands.py
Normal file
25
Parallelism/Commands.py
Normal file
@ -0,0 +1,25 @@
|
||||
from enum import Enum
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
class VCommandsType(Enum):
|
||||
PREV = 'Prev'
|
||||
SKIP = 'Skip'
|
||||
PAUSE = 'Pause'
|
||||
RESUME = 'Resume'
|
||||
CONTEXT = 'Context'
|
||||
PLAY = 'Play'
|
||||
STOP = 'Stop'
|
||||
RESET = 'Reset'
|
||||
|
||||
|
||||
class VCommands:
|
||||
def __init__(self, type: VCommandsType, args=None) -> None:
|
||||
self.__type = type
|
||||
self.__args = args
|
||||
|
||||
def getType(self) -> VCommandsType:
|
||||
return self.__type
|
||||
|
||||
def getArgs(self) -> Tuple:
|
||||
return self.__args
|
||||
380
Parallelism/PlayerProcess.py
Normal file
380
Parallelism/PlayerProcess.py
Normal file
@ -0,0 +1,380 @@
|
||||
import asyncio
|
||||
from os import listdir
|
||||
from discord import Intents, User, Member, Message, Embed
|
||||
from asyncio import AbstractEventLoop, Semaphore
|
||||
from multiprocessing import Process, Queue, RLock
|
||||
from threading import Lock, Thread
|
||||
from typing import Callable, List
|
||||
from discord import Client, Guild, FFmpegPCMAudio, VoiceChannel, TextChannel
|
||||
from Music.Playlist import Playlist
|
||||
from Music.Song import Song
|
||||
from Config.Configs import Configs
|
||||
from Config.Messages import Messages
|
||||
from discord.ext.commands import Bot
|
||||
from Views.Embeds import Embeds
|
||||
from Parallelism.Commands import VCommands, VCommandsType
|
||||
|
||||
|
||||
class TimeoutClock:
|
||||
def __init__(self, callback: Callable, loop: asyncio.AbstractEventLoop):
|
||||
self.__callback = callback
|
||||
self.__task = loop.create_task(self.__executor())
|
||||
|
||||
async def __executor(self):
|
||||
await asyncio.sleep(Configs().VC_TIMEOUT)
|
||||
await self.__callback()
|
||||
|
||||
def cancel(self):
|
||||
self.__task.cancel()
|
||||
|
||||
|
||||
class PlayerProcess(Process):
|
||||
"""Process that will play songs, receive commands from the main process by a Queue"""
|
||||
|
||||
def __init__(self, name: str, playlist: Playlist, lock: Lock, queue: Queue, guildID: int, textID: int, voiceID: int, authorID: int) -> None:
|
||||
"""
|
||||
Start a new process that will have his own bot instance
|
||||
Due to pickle serialization, no objects are stored, the values initialization are being made in the run method
|
||||
"""
|
||||
Process.__init__(self, name=name, group=None, target=None, args=(), kwargs={})
|
||||
# Synchronization objects
|
||||
self.__playlist: Playlist = playlist
|
||||
self.__playlistLock: Lock = lock
|
||||
self.__queue: Queue = queue
|
||||
self.__semStopPlaying: Semaphore = None
|
||||
self.__loop: AbstractEventLoop = None
|
||||
# Discord context ID
|
||||
self.__textChannelID = textID
|
||||
self.__guildID = guildID
|
||||
self.__voiceChannelID = voiceID
|
||||
self.__authorID = authorID
|
||||
# All information of discord context will be retrieved directly with discord API
|
||||
self.__guild: Guild = None
|
||||
self.__bot: Client = None
|
||||
self.__voiceChannel: VoiceChannel = None
|
||||
self.__textChannel: TextChannel = None
|
||||
self.__author: User = None
|
||||
self.__botMember: Member = None
|
||||
|
||||
self.__configs: Configs = None
|
||||
self.__embeds: Embeds = None
|
||||
self.__messages: Messages = None
|
||||
self.__messagesToDelete: List[Message] = []
|
||||
self.__playing = False
|
||||
self.__forceStop = False
|
||||
self.FFMPEG_OPTIONS = {'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5',
|
||||
'options': '-vn'}
|
||||
|
||||
def run(self) -> None:
|
||||
"""Method called by process.start(), this will exec the actually _run method in a event loop"""
|
||||
try:
|
||||
print(f'Starting Process {self.name}')
|
||||
self.__playerLock = RLock()
|
||||
self.__loop = asyncio.get_event_loop()
|
||||
|
||||
self.__configs = Configs()
|
||||
self.__messages = Messages()
|
||||
self.__embeds = Embeds()
|
||||
|
||||
self.__semStopPlaying = Semaphore(0)
|
||||
self.__loop.run_until_complete(self._run())
|
||||
except Exception as e:
|
||||
print(f'[Error in Process {self.name}] -> {e}')
|
||||
|
||||
async def _run(self) -> None:
|
||||
# Recreate the bot instance and objects using discord API
|
||||
self.__bot = await self.__createBotInstance()
|
||||
self.__guild = self.__bot.get_guild(self.__guildID)
|
||||
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()
|
||||
|
||||
# Start the timeout function
|
||||
self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop)
|
||||
# Thread that will receive commands to be executed in this Process
|
||||
self.__commandsReceiver = Thread(target=self.__commandsReceiver, daemon=True)
|
||||
self.__commandsReceiver.start()
|
||||
|
||||
# Start a Task to play songs
|
||||
self.__loop.create_task(self.__playPlaylistSongs())
|
||||
# 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:
|
||||
if not self.__playing:
|
||||
with self.__playlistLock:
|
||||
song = self.__playlist.next_song()
|
||||
|
||||
if song is not None:
|
||||
self.__loop.create_task(self.__playSong(song), name=f'Song {song.identifier}')
|
||||
|
||||
async def __playSong(self, song: Song) -> None:
|
||||
"""Function that will trigger the player to play the song"""
|
||||
try:
|
||||
self.__playerLock.acquire()
|
||||
if song is None:
|
||||
return
|
||||
|
||||
if song.source is None:
|
||||
return self.__playNext(None)
|
||||
|
||||
# If not connected, connect to bind channel
|
||||
if self.__guild.voice_client is None:
|
||||
await self.__connectToVoiceChannel()
|
||||
|
||||
# If the player is already playing return
|
||||
if self.__guild.voice_client.is_playing():
|
||||
return
|
||||
|
||||
self.__playing = True
|
||||
self.__playingSong = song
|
||||
|
||||
player = FFmpegPCMAudio(song.source, **self.FFMPEG_OPTIONS)
|
||||
self.__guild.voice_client.play(player, after=lambda e: self.__playNext(e))
|
||||
|
||||
self.__timer.cancel()
|
||||
self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop)
|
||||
|
||||
await self.__deletePrevNowPlaying()
|
||||
await self.__showNowPlaying()
|
||||
except Exception as e:
|
||||
print(f'[ERROR IN PLAY SONG] -> {e}, {type(e)}')
|
||||
self.__playNext(None)
|
||||
finally:
|
||||
self.__playerLock.release()
|
||||
|
||||
def __playNext(self, error) -> None:
|
||||
with self.__playerLock:
|
||||
if self.__forceStop: # If it's forced to stop player
|
||||
self.__forceStop = False
|
||||
return None
|
||||
|
||||
with self.__playlistLock:
|
||||
song = self.__playlist.next_song()
|
||||
|
||||
if song is not None:
|
||||
self.__loop.create_task(self.__playSong(song), name=f'Song {song.identifier}')
|
||||
else:
|
||||
with self.__playlistLock:
|
||||
self.__playlist.loop_off()
|
||||
self.__playingSong = None
|
||||
self.__playing = False
|
||||
|
||||
async def __playPrev(self, voiceChannelID: int) -> None:
|
||||
with self.__playlistLock:
|
||||
song = self.__playlist.prev_song()
|
||||
|
||||
if song is not None:
|
||||
if self.__guild.voice_client is None: # If not connect, connect to the user voice channel
|
||||
self.__voiceChannelID = voiceChannelID
|
||||
self.__voiceChannel = self.__guild.get_channel(self.__voiceChannelID)
|
||||
await self.__connectToVoiceChannel()
|
||||
|
||||
# If already playing, stop the current play
|
||||
if self.__guild.voice_client.is_playing() or self.__guild.voice_client.is_paused():
|
||||
# Will forbidden next_song to execute after stopping current player
|
||||
self.__forceStop = True
|
||||
self.__guild.voice_client.stop()
|
||||
self.__playing = False
|
||||
|
||||
self.__loop.create_task(self.__playSong(song), name=f'Song {song.identifier}')
|
||||
|
||||
def __commandsReceiver(self) -> None:
|
||||
while True:
|
||||
command: VCommands = self.__queue.get()
|
||||
type = command.getType()
|
||||
args = command.getArgs()
|
||||
|
||||
try:
|
||||
self.__playerLock.acquire()
|
||||
if type == VCommandsType.PAUSE:
|
||||
self.__pause()
|
||||
elif type == VCommandsType.RESUME:
|
||||
self.__resume()
|
||||
elif type == VCommandsType.SKIP:
|
||||
self.__skip()
|
||||
elif type == VCommandsType.PLAY:
|
||||
asyncio.run_coroutine_threadsafe(self.__playPlaylistSongs(), self.__loop)
|
||||
elif type == VCommandsType.PREV:
|
||||
asyncio.run_coroutine_threadsafe(self.__playPrev(args), self.__loop)
|
||||
elif type == VCommandsType.RESET:
|
||||
asyncio.run_coroutine_threadsafe(self.__reset(), self.__loop)
|
||||
elif type == VCommandsType.STOP:
|
||||
asyncio.run_coroutine_threadsafe(self.__stop(), self.__loop)
|
||||
else:
|
||||
print(f'[ERROR] -> Unknown Command Received: {command}')
|
||||
except Exception as e:
|
||||
print(f'[ERROR IN COMMAND RECEIVER] -> {type} - {e}')
|
||||
finally:
|
||||
self.__playerLock.release()
|
||||
|
||||
def __pause(self) -> None:
|
||||
if self.__guild.voice_client is not None:
|
||||
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():
|
||||
with self.__playlistLock:
|
||||
self.__playlist.clear()
|
||||
self.__playlist.loop_off()
|
||||
|
||||
self.__guild.voice_client.stop()
|
||||
self.__playingSong = None
|
||||
await self.__guild.voice_client.disconnect()
|
||||
self.__semStopPlaying.release()
|
||||
|
||||
def __resume(self) -> None:
|
||||
# Lock to work with Player
|
||||
with self.__playerLock:
|
||||
if self.__guild.voice_client is not None:
|
||||
if self.__guild.voice_client.is_paused():
|
||||
self.__guild.voice_client.resume()
|
||||
|
||||
def __skip(self) -> None:
|
||||
# Lock to work with Player
|
||||
with self.__playerLock:
|
||||
if self.__guild.voice_client is not None and self.__playing:
|
||||
self.__playing = False
|
||||
self.__guild.voice_client.stop()
|
||||
|
||||
async def __forceStop(self) -> None:
|
||||
# Lock to work with Player
|
||||
with self.__playerLock:
|
||||
if self.__guild.voice_client is None:
|
||||
return
|
||||
|
||||
self.__guild.voice_client.stop()
|
||||
await self.__guild.voice_client.disconnect()
|
||||
with self.__playlistLock:
|
||||
self.__playlist.clear()
|
||||
self.__playlist.loop_off()
|
||||
|
||||
async def __createBotInstance(self) -> Client:
|
||||
"""Load a new bot instance that should not be directly called.
|
||||
Get the guild, voice and text Channel in discord API using IDs passed in constructor
|
||||
"""
|
||||
intents = Intents.default()
|
||||
intents.members = True
|
||||
bot = Bot(command_prefix='Rafael',
|
||||
pm_help=True,
|
||||
case_insensitive=True,
|
||||
intents=intents)
|
||||
bot.remove_command('help')
|
||||
|
||||
# Add the Cogs for this bot too
|
||||
for filename in listdir(f'./{self.__configs.COMMANDS_PATH}'):
|
||||
if filename.endswith('.py'):
|
||||
bot.load_extension(f'{self.__configs.COMMANDS_PATH}.{filename[:-3]}')
|
||||
|
||||
# Login and connect the bot instance to discord API
|
||||
task = self.__loop.create_task(bot.login(token=self.__configs.BOT_TOKEN, bot=True))
|
||||
await task
|
||||
self.__loop.create_task(bot.connect(reconnect=True))
|
||||
# Sleep to wait connection to be established
|
||||
await self.__ensureDiscordConnection(bot)
|
||||
|
||||
return bot
|
||||
|
||||
async def __timeoutHandler(self) -> None:
|
||||
try:
|
||||
if self.__guild.voice_client is None:
|
||||
return
|
||||
|
||||
if self.__guild.voice_client.is_playing() or self.__guild.voice_client.is_paused():
|
||||
self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop)
|
||||
|
||||
elif self.__guild.voice_client.is_connected():
|
||||
with self.__playerLock:
|
||||
with self.__playlistLock:
|
||||
self.__playlist.clear()
|
||||
self.__playlist.loop_off()
|
||||
self.__playing = False
|
||||
await self.__guild.voice_client.disconnect()
|
||||
# Release semaphore to finish process
|
||||
self.__semStopPlaying.release()
|
||||
except Exception as e:
|
||||
print(f'[Error in Timeout] -> {e}')
|
||||
|
||||
async def __ensureDiscordConnection(self, bot: Client) -> None:
|
||||
"""Await in this point until connection to discord is established"""
|
||||
guild = None
|
||||
while guild is None:
|
||||
guild = bot.get_guild(self.__guildID)
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
async def __connectToVoiceChannel(self) -> bool:
|
||||
try:
|
||||
await self.__voiceChannel.connect(reconnect=True, timeout=None)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f'[ERROR CONNECTING TO VC] -> {e}')
|
||||
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
|
||||
|
||||
async def __showNowPlaying(self) -> None:
|
||||
# Get the lock of the playlist
|
||||
with self.__playlistLock:
|
||||
if not self.__playing or self.__playingSong is None:
|
||||
embed = self.__embeds.NOT_PLAYING()
|
||||
await self.__textChannel.send(embed=embed)
|
||||
return
|
||||
|
||||
if self.__playlist.isLoopingOne():
|
||||
title = self.__messages.ONE_SONG_LOOPING
|
||||
else:
|
||||
title = self.__messages.SONG_PLAYING
|
||||
|
||||
info = self.__playingSong.info
|
||||
embed = self.__embeds.SONG_INFO(info, title)
|
||||
await self.__textChannel.send(embed=embed)
|
||||
self.__messagesToDelete.append(await self.__getSendedMessage())
|
||||
|
||||
async def __deletePrevNowPlaying(self) -> None:
|
||||
for message in self.__messagesToDelete:
|
||||
try:
|
||||
await message.delete()
|
||||
except:
|
||||
pass
|
||||
self.__messagesToDelete.clear()
|
||||
|
||||
async def __getSendedMessage(self) -> Message:
|
||||
stringToIdentify = 'Uploader:'
|
||||
last_messages: List[Message] = await self.__textChannel.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
|
||||
29
Parallelism/ProcessInfo.py
Normal file
29
Parallelism/ProcessInfo.py
Normal file
@ -0,0 +1,29 @@
|
||||
from multiprocessing import Process, Queue, Lock
|
||||
from Music.Playlist import Playlist
|
||||
|
||||
|
||||
class ProcessInfo:
|
||||
"""
|
||||
Class to store the reference to all structures to maintain a player process
|
||||
"""
|
||||
|
||||
def __init__(self, process: Process, queue: Queue, playlist: Playlist, lock: Lock) -> None:
|
||||
self.__process = process
|
||||
self.__queue = queue
|
||||
self.__playlist = playlist
|
||||
self.__lock = lock
|
||||
|
||||
def setProcess(self, newProcess: Process) -> None:
|
||||
self.__process = newProcess
|
||||
|
||||
def getProcess(self) -> Process:
|
||||
return self.__process
|
||||
|
||||
def getQueue(self) -> Queue:
|
||||
return self.__queue
|
||||
|
||||
def getPlaylist(self) -> Playlist:
|
||||
return self.__playlist
|
||||
|
||||
def getLock(self) -> Lock:
|
||||
return self.__lock
|
||||
101
Parallelism/ProcessManager.py
Normal file
101
Parallelism/ProcessManager.py
Normal file
@ -0,0 +1,101 @@
|
||||
from multiprocessing import Queue, Lock
|
||||
from multiprocessing.managers import BaseManager, NamespaceProxy
|
||||
from typing import Dict
|
||||
from Config.Singleton import Singleton
|
||||
from discord import Guild
|
||||
from discord.ext.commands import Context
|
||||
from Parallelism.PlayerProcess import PlayerProcess
|
||||
from Music.Playlist import Playlist
|
||||
from Parallelism.ProcessInfo import ProcessInfo
|
||||
from Parallelism.Commands import VCommands, VCommandsType
|
||||
|
||||
|
||||
class ProcessManager(Singleton):
|
||||
"""
|
||||
Manage all running player process, creating and storing them for future calls
|
||||
Deal with the creation of shared memory
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
if not super().created:
|
||||
VManager.register('Playlist', Playlist)
|
||||
self.__manager = VManager()
|
||||
self.__manager.start()
|
||||
self.__playersProcess: Dict[Guild, ProcessInfo] = {}
|
||||
|
||||
def setPlayerContext(self, guild: Guild, context: ProcessInfo):
|
||||
self.__playersProcess[guild.id] = context
|
||||
|
||||
def getPlayerInfo(self, guild: Guild, context: Context) -> ProcessInfo:
|
||||
"""Return the process info for the guild, if not, create one"""
|
||||
try:
|
||||
if guild.id not in self.__playersProcess.keys():
|
||||
self.__playersProcess[guild.id] = self.__createProcessInfo(context)
|
||||
else:
|
||||
# If the process has ended create a new one
|
||||
if not self.__playersProcess[guild.id].getProcess().is_alive():
|
||||
self.__playersProcess[guild.id] = self.__recreateProcess(context)
|
||||
|
||||
return self.__playersProcess[guild.id]
|
||||
except Exception as e:
|
||||
print(f'[Error In GetPlayerContext] -> {e}')
|
||||
|
||||
def resetProcess(self, guild: Guild, context: Context) -> None:
|
||||
"""Restart a running process, already start it to return to play"""
|
||||
if guild.id not in self.__playersProcess.keys():
|
||||
return None
|
||||
|
||||
# Recreate the process keeping the playlist
|
||||
newProcessInfo = self.__recreateProcess(context)
|
||||
newProcessInfo.getProcess().start() # Start the process
|
||||
# Send a command to start the play again
|
||||
playCommand = VCommands(VCommandsType.PLAY)
|
||||
newProcessInfo.getQueue().put(playCommand)
|
||||
self.__playersProcess[guild.id] = newProcessInfo
|
||||
|
||||
def getRunningPlayerInfo(self, guild: Guild) -> ProcessInfo:
|
||||
"""Return the process info for the guild, if not, return None"""
|
||||
if guild.id not in self.__playersProcess.keys():
|
||||
return None
|
||||
|
||||
return self.__playersProcess[guild.id]
|
||||
|
||||
def __createProcessInfo(self, context: Context) -> ProcessInfo:
|
||||
guildID: int = context.guild.id
|
||||
textID: int = context.channel.id
|
||||
voiceID: int = context.author.voice.channel.id
|
||||
authorID: int = context.author.id
|
||||
|
||||
playlist: Playlist = self.__manager.Playlist()
|
||||
lock = Lock()
|
||||
queue = Queue()
|
||||
process = PlayerProcess(context.guild.name, playlist, lock, queue,
|
||||
guildID, textID, voiceID, authorID)
|
||||
processInfo = ProcessInfo(process, queue, playlist, lock)
|
||||
|
||||
return processInfo
|
||||
|
||||
def __recreateProcess(self, context: Context) -> ProcessInfo:
|
||||
"""Create a new process info using previous playlist"""
|
||||
guildID: int = context.guild.id
|
||||
textID: int = context.channel.id
|
||||
voiceID: int = context.author.voice.channel.id
|
||||
authorID: int = context.author.id
|
||||
|
||||
playlist: Playlist = self.__playersProcess[guildID].getPlaylist()
|
||||
lock = Lock()
|
||||
queue = Queue()
|
||||
|
||||
process = PlayerProcess(context.guild.name, playlist, lock, queue,
|
||||
guildID, textID, voiceID, authorID)
|
||||
processInfo = ProcessInfo(process, queue, playlist, lock)
|
||||
|
||||
return processInfo
|
||||
|
||||
|
||||
class VManager(BaseManager):
|
||||
pass
|
||||
|
||||
|
||||
class VProxy(NamespaceProxy):
|
||||
_exposed_ = ('__getattribute__', '__setattr__', '__delattr__')
|
||||
@ -1,5 +1,5 @@
|
||||
from Tests.TestBase import VulkanTesterBase
|
||||
from Exceptions.Exceptions import DeezerError
|
||||
from Config.Exceptions import DeezerError
|
||||
|
||||
|
||||
class VulkanDeezerTest(VulkanTesterBase):
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
from Tests.TestBase import VulkanTesterBase
|
||||
from Exceptions.Exceptions import SpotifyError
|
||||
from Config.Exceptions import SpotifyError
|
||||
|
||||
|
||||
class VulkanSpotifyTest(VulkanTesterBase):
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
from discord.ext.commands import Context
|
||||
from discord import Embed
|
||||
|
||||
|
||||
class Sender:
|
||||
@classmethod
|
||||
async def send_embed(cls, ctx: Context, embed: Embed) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
async def send_message(cls, ctx: Context, message: Embed) -> None:
|
||||
pass
|
||||
@ -32,19 +32,6 @@ class Utils:
|
||||
return False
|
||||
|
||||
|
||||
class Timer:
|
||||
def __init__(self, callback):
|
||||
self.__callback = callback
|
||||
self.__task = asyncio.create_task(self.__executor())
|
||||
|
||||
async def __executor(self):
|
||||
await asyncio.sleep(config.VC_TIMEOUT)
|
||||
await self.__callback()
|
||||
|
||||
def cancel(self):
|
||||
self.__task.cancel()
|
||||
|
||||
|
||||
def run_async(func):
|
||||
@wraps(func)
|
||||
async def run(*args, loop=None, executor=None, **kwargs):
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from Controllers.ControllerResponse import ControllerResponse
|
||||
from Handlers.HandlerResponse import HandlerResponse
|
||||
from discord.ext.commands import Context
|
||||
from discord import Client, Message
|
||||
|
||||
|
||||
class AbstractView(ABC):
|
||||
def __init__(self, response: ControllerResponse) -> None:
|
||||
self.__response: ControllerResponse = response
|
||||
def __init__(self, response: HandlerResponse) -> None:
|
||||
self.__response: HandlerResponse = response
|
||||
self.__context: Context = response.ctx
|
||||
self.__message: Message = response.ctx.message
|
||||
self.__bot: Client = response.ctx.bot
|
||||
|
||||
@property
|
||||
def response(self) -> ControllerResponse:
|
||||
def response(self) -> HandlerResponse:
|
||||
return self.__response
|
||||
|
||||
@property
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
from Views.AbstractView import AbstractView
|
||||
from Controllers.ControllerResponse import ControllerResponse
|
||||
from Handlers.HandlerResponse import HandlerResponse
|
||||
|
||||
|
||||
class EmbedView(AbstractView):
|
||||
def __init__(self, response: ControllerResponse) -> None:
|
||||
def __init__(self, response: HandlerResponse) -> None:
|
||||
super().__init__(response)
|
||||
|
||||
async def run(self) -> None:
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
from Config.Messages import Messages
|
||||
from Exceptions.Exceptions import VulkanError
|
||||
from Config.Exceptions import VulkanError
|
||||
from discord import Embed
|
||||
from Config.Configs import Configs
|
||||
from Config.Colors import Colors
|
||||
@ -164,7 +164,7 @@ class Embeds:
|
||||
|
||||
def COMMAND_NOT_FOUND(self) -> Embed:
|
||||
embed = Embed(
|
||||
title=self.__messages.ERROR_TITLE,
|
||||
title=self.__messages.COMMAND_NOT_FOUND_TITLE,
|
||||
description=self.__messages.COMMAND_NOT_FOUND,
|
||||
colour=self.__colors.BLACK
|
||||
)
|
||||
@ -231,6 +231,13 @@ class Embeds:
|
||||
colour=self.__colors.BLACK)
|
||||
return embed
|
||||
|
||||
def PLAYER_RESTARTED(self) -> Embed:
|
||||
embed = Embed(
|
||||
title=self.__messages.ERROR_TITLE,
|
||||
description=self.__messages.ERROR_IN_PROCESS,
|
||||
colour=self.__colors.BLACK)
|
||||
return embed
|
||||
|
||||
def NO_CHANNEL(self) -> Embed:
|
||||
embed = Embed(
|
||||
title=self.__messages.IMPOSSIBLE_MOVE,
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
from Views.AbstractView import AbstractView
|
||||
from Controllers.ControllerResponse import ControllerResponse
|
||||
from Handlers.HandlerResponse import HandlerResponse
|
||||
|
||||
|
||||
class EmoteView(AbstractView):
|
||||
|
||||
def __init__(self, response: ControllerResponse) -> None:
|
||||
def __init__(self, response: HandlerResponse) -> None:
|
||||
super().__init__(response)
|
||||
|
||||
async def run(self) -> None:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user