Merge pull request #21 from RafaelSolVargas/usingProcess

Upgrading the architecture to use both asyncio and multiprocessing modules
This commit is contained in:
Rafael Vargas 2022-07-25 09:54:16 -03:00 committed by GitHub
commit 48d7166386
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 1595 additions and 1037 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.vscode
assets/
__pycache__
.env

View File

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

View File

@ -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]({}).

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +0,0 @@
class Database:
def __init__(self) -> None:
pass

View File

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

View File

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

View File

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

View File

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

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

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

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

View File

@ -1,5 +1,5 @@
import deezer
from Exceptions.Exceptions import DeezerError
from Config.Exceptions import DeezerError
from Config.Messages import DeezerMessages

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View 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

View 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__')

View File

@ -1,5 +1,5 @@
from Tests.TestBase import VulkanTesterBase
from Exceptions.Exceptions import DeezerError
from Config.Exceptions import DeezerError
class VulkanDeezerTest(VulkanTesterBase):

View File

@ -1,5 +1,5 @@
from Tests.TestBase import VulkanTesterBase
from Exceptions.Exceptions import SpotifyError
from Config.Exceptions import SpotifyError
class VulkanSpotifyTest(VulkanTesterBase):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -33,5 +33,6 @@ class VulkanInitializer:
self.__bot.run(self.__config.BOT_TOKEN, bot=True, reconnect=True)
vulkan = VulkanInitializer()
vulkan.run()
if __name__ == '__main__':
vulkan = VulkanInitializer()
vulkan.run()