Merge pull request #22 from RafaelSolVargas/upgradingUI

Creating Buttons for Commands
This commit is contained in:
Rafael Vargas 2022-07-29 00:46:31 -03:00 committed by GitHub
commit a5cecd85d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 985 additions and 513 deletions

1
.gitignore vendored
View File

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

BIN
Assets/playermenu.jfif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
Assets/vulkan-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

BIN
Assets/vulkancommands.jfif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

View File

@ -1,7 +1,7 @@
from Config.Singleton import Singleton
class Colors(Singleton):
class VColors(Singleton):
def __init__(self) -> None:
self.__red = 0xDC143C
self.__green = 0x1F8B4C

View File

@ -2,7 +2,7 @@ from decouple import config
from Config.Singleton import Singleton
class Configs(Singleton):
class VConfigs(Singleton):
def __init__(self) -> None:
if not super().created:
self.BOT_PREFIX = '!'
@ -18,7 +18,7 @@ class Configs(Singleton):
self.CLEANER_MESSAGES_QUANT = 5
self.ACQUIRE_LOCK_TIMEOUT = 10
self.COMMANDS_PATH = 'DiscordCogs'
self.VC_TIMEOUT = 600
self.VC_TIMEOUT = 300
self.MAX_PLAYLIST_LENGTH = 50
self.MAX_PLAYLIST_FORCED_LENGTH = 5
@ -30,3 +30,9 @@ class Configs(Singleton):
self.MY_ERROR_BAD_COMMAND = 'This string serves to verify if some error was raised by myself on purpose'
self.INVITE_URL = 'https://discordapp.com/oauth2/authorize?client_id={}&scope=bot'
def getProcessManager(self):
return self.__manager
def setProcessManager(self, newManager):
self.__manager = newManager

View File

@ -1,16 +1,16 @@
from Config.Messages import Messages
from Config.Exceptions import VulkanError
from discord import Embed
from Config.Configs import Configs
from Config.Colors import Colors
from Config.Configs import VConfigs
from Config.Colors import VColors
from datetime import timedelta
class Embeds:
class VEmbeds:
def __init__(self) -> None:
self.__config = Configs()
self.__config = VConfigs()
self.__messages = Messages()
self.__colors = Colors()
self.__colors = VColors()
def ONE_SONG_LOOPING(self, info: dict) -> Embed:
title = self.__messages.ONE_SONG_LOOPING
@ -334,7 +334,7 @@ class Embeds:
def CARA_COROA(self, result: str) -> Embed:
embed = Embed(
title='Cara Cora',
title='Cara Coroa',
description=f'Result: {result}',
colour=self.__colors.GREEN
)

20
Config/Emojis.py Normal file
View File

@ -0,0 +1,20 @@
from Config.Singleton import Singleton
class VEmojis(Singleton):
def __init__(self) -> None:
if not super().created:
self.SKIP = ""
self.BACK = ""
self.PAUSE = "⏸️"
self.PLAY = "▶️"
self.STOP = "⏹️"
self.LOOP_ONE = "🔂"
self.LOOP_OFF = "➡️"
self.LOOP_ALL = "🔁"
self.SHUFFLE = "🔀"
self.QUEUE = "📜"
self.MUSIC = "🎧"
self.ERROR = ""
self.DOWNLOADING = "📥"
self.SUCCESS = ""

View File

@ -1,11 +1,11 @@
from Config.Singleton import Singleton
from Config.Configs import Configs
from Config.Configs import VConfigs
class Helper(Singleton):
def __init__(self) -> None:
if not super().created:
config = Configs()
config = VConfigs()
self.HELP_SKIP = 'Skip the current playing song.'
self.HELP_SKIP_LONG = 'Skip the playing of the current song, does not work if loop one is activated. \n\nArguments: None.'
self.HELP_RESUME = 'Resumes the song player.'

View File

@ -1,11 +1,13 @@
from Config.Singleton import Singleton
from Config.Configs import Configs
from Config.Configs import VConfigs
from Config.Emojis import VEmojis
class Messages(Singleton):
def __init__(self) -> None:
if not super().created:
configs = Configs()
self.__emojis = VEmojis()
configs = VConfigs()
self.STARTUP_MESSAGE = 'Starting Vulkan...'
self.STARTUP_COMPLETE_MESSAGE = 'Vulkan is now operating.'
@ -16,67 +18,67 @@ class Messages(Singleton):
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'
self.QUEUE_TITLE = '🎧 Songs in Queue'
self.ONE_SONG_LOOPING = '🎧 Looping One Song'
self.ALL_SONGS_LOOPING = '🎧 Looping All Songs'
self.SONG_PAUSED = '⏸️ Song paused'
self.SONG_RESUMED = '▶️ Song playing'
self.EMPTY_QUEUE = f'📜 Song queue is empty, use {configs.BOT_PREFIX}play to add new songs'
self.SONG_DOWNLOADING = '📥 Downloading...'
self.SONG_ADDED_TWO = f'{self.__emojis.MUSIC} Song added to the queue'
self.SONG_PLAYING = f'{self.__emojis.MUSIC} Song playing now'
self.SONG_PLAYER = f'{self.__emojis.MUSIC} Song Player'
self.QUEUE_TITLE = f'{self.__emojis.MUSIC} Songs in Queue'
self.ONE_SONG_LOOPING = f'{self.__emojis.MUSIC} Looping One Song'
self.ALL_SONGS_LOOPING = f'{self.__emojis.MUSIC} Looping All Songs'
self.SONG_PAUSED = f'{self.__emojis.PAUSE} Song paused'
self.SONG_RESUMED = f'{self.__emojis.PLAY} Song playing'
self.EMPTY_QUEUE = f'{self.__emojis.QUEUE} Song queue is empty, use {configs.BOT_PREFIX}play to add new songs'
self.SONG_DOWNLOADING = f'{self.__emojis.DOWNLOADING} Downloading...'
self.HISTORY_TITLE = '🎧 Played Songs'
self.HISTORY_EMPTY = '📜 There is no musics in history'
self.HISTORY_TITLE = f'{self.__emojis.MUSIC} Played Songs'
self.HISTORY_EMPTY = f'{self.__emojis.QUEUE} There is no musics in history'
self.SONG_MOVED_SUCCESSFULLY = 'Song `{}` in position `{}` moved to the position `{}` successfully'
self.SONG_REMOVED_SUCCESSFULLY = 'Song `{}` removed successfully'
self.LOOP_ALL_ON = f' Vulkan is looping all songs, use {configs.BOT_PREFIX}loop off to disable this loop first'
self.LOOP_ONE_ON = f' Vulkan is looping one song, use {configs.BOT_PREFIX}loop off to disable this loop first'
self.LOOP_ALL_ALREADY_ON = '🔁 Vulkan is already looping all songs'
self.LOOP_ONE_ALREADY_ON = '🔂 Vulkan is already looping the current song'
self.LOOP_ALL_ACTIVATE = '🔁 Looping all songs'
self.LOOP_ONE_ACTIVATE = '🔂 Looping the current song'
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.
self.LOOP_ALL_ON = f'{self.__emojis.ERROR} Vulkan is looping all songs, use {configs.BOT_PREFIX}loop off to disable this loop first'
self.LOOP_ONE_ON = f'{self.__emojis.ERROR} Vulkan is looping one song, use {configs.BOT_PREFIX}loop off to disable this loop first'
self.LOOP_ALL_ALREADY_ON = f'{self.__emojis.LOOP_ALL} Vulkan is already looping all songs'
self.LOOP_ONE_ALREADY_ON = f'{self.__emojis.LOOP_ONE} Vulkan is already looping the current song'
self.LOOP_ALL_ACTIVATE = f'{self.__emojis.LOOP_ALL} Looping all songs'
self.LOOP_ONE_ACTIVATE = f'{self.__emojis.LOOP_ONE} Looping the current song'
self.LOOP_DISABLE = f'{self.__emojis.LOOP_OFF} Loop disabled'
self.LOOP_ALREADY_DISABLE = f'{self.__emojis.ERROR} Loop is already disabled'
self.LOOP_ON = f'{self.__emojis.ERROR} This command cannot be invoked with any loop activated. Use {configs.BOT_PREFIX}loop off to disable loop'
self.BAD_USE_OF_LOOP = f"""{self.__emojis.ERROR} 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'
self.ERROR_MOVING = ' Error while moving the songs'
self.LENGTH_ERROR = ' Numbers must be between 1 and queue length, use -1 for the last song'
self.ERROR_NUMBER = ' This command require a number'
self.ERROR_PLAYING = ' Error while playing songs'
self.COMMAND_NOT_FOUND = f' Command not found, type {configs.BOT_PREFIX}help to see all commands'
self.UNKNOWN_ERROR = f' Unknown Error, if needed, use {configs.BOT_PREFIX}reset to reset the player of your server'
self.ERROR_MISSING_ARGUMENTS = f' Missing arguments in this command. Type {configs.BOT_PREFIX}help "command" to see more info about this command'
self.NOT_PREVIOUS = ' There is none previous song to play'
self.PLAYER_NOT_PLAYING = f' No song playing. Use {configs.BOT_PREFIX}play to start the player'
self.SONGS_SHUFFLED = f'{self.__emojis.SHUFFLE} Songs shuffled successfully'
self.ERROR_SHUFFLING = f'{self.__emojis.ERROR} Error while shuffling the songs'
self.ERROR_MOVING = f'{self.__emojis.ERROR} Error while moving the songs'
self.LENGTH_ERROR = f'{self.__emojis.ERROR} Numbers must be between 1 and queue length, use -1 for the last song'
self.ERROR_NUMBER = f'{self.__emojis.ERROR} This command require a number'
self.ERROR_PLAYING = f'{self.__emojis.ERROR} Error while playing songs'
self.COMMAND_NOT_FOUND = f'{self.__emojis.ERROR} Command not found, type {configs.BOT_PREFIX}help to see all commands'
self.UNKNOWN_ERROR = f'{self.__emojis.ERROR} Unknown Error, if needed, use {configs.BOT_PREFIX}reset to reset the player of your server'
self.ERROR_MISSING_ARGUMENTS = f'{self.__emojis.ERROR} Missing arguments in this command. Type {configs.BOT_PREFIX}help "command" to see more info about this command'
self.NOT_PREVIOUS = f'{self.__emojis.ERROR} There is none previous song to play'
self.PLAYER_NOT_PLAYING = f'{self.__emojis.ERROR} 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 = " It's impossible to download and play this video"
self.EXTRACTING_ERROR = ' An error ocurred while searching for the songs'
self.DOWNLOADING_ERROR = f"{self.__emojis.ERROR} It's impossible to download and play this video"
self.EXTRACTING_ERROR = f'{self.__emojis.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.ERROR_IN_PROCESS = f"{self.__emojis.ERROR} 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'
self.VIDEO_UNAVAILABLE = ' Sorry. This video is unavailable for download.'
self.ERROR_DUE_LOOP_ONE_ON = f' This command cannot be executed with loop one activated. Use {configs.BOT_PREFIX}loop off to disable loop.'
self.BAD_COMMAND = f'{self.__emojis.ERROR} Bad usage of this command, type {configs.BOT_PREFIX}help "command" to understand the command better'
self.VIDEO_UNAVAILABLE = f'{self.__emojis.ERROR} Sorry. This video is unavailable for download.'
self.ERROR_DUE_LOOP_ONE_ON = f'{self.__emojis.ERROR} This command cannot be executed with loop one activated. Use {configs.BOT_PREFIX}loop off to disable loop.'
class SearchMessages(Singleton):
def __init__(self) -> None:
if not super().created:
config = Configs()
config = VConfigs()
self.UNKNOWN_INPUT = f'This type of input was too strange, try something else or type {config.BOT_PREFIX}help play'
self.UNKNOWN_INPUT_TITLE = 'Nothing Found'
self.GENERIC_TITLE = 'URL could not be processed'

View File

@ -1,24 +1,22 @@
from discord import Client, Game, Status, Embed
from discord.ext.commands.errors import CommandNotFound, MissingRequiredArgument
from discord.ext import commands
from Config.Configs import Configs
from discord import Embed
from discord.ext.commands import Cog, command
from Config.Configs import VConfigs
from Config.Helper import Helper
from Config.Messages import Messages
from Config.Colors import Colors
from Views.Embeds import Embeds
from Config.Colors import VColors
from Music.VulkanBot import VulkanBot
from Config.Embeds import VEmbeds
helper = Helper()
class ControlCog(commands.Cog):
class ControlCog(Cog):
"""Class to handle discord events"""
def __init__(self, bot: Client):
def __init__(self, bot: VulkanBot):
self.__bot = bot
self.__config = Configs()
self.__messages = Messages()
self.__colors = Colors()
self.__embeds = Embeds()
self.__config = VConfigs()
self.__colors = VColors()
self.__embeds = VEmbeds()
self.__commands = {
'MUSIC': ['resume', 'pause', 'loop', 'stop',
'skip', 'play', 'queue', 'clear',
@ -28,28 +26,7 @@ class ControlCog(commands.Cog):
}
@commands.Cog.listener()
async def on_ready(self):
print(self.__messages.STARTUP_MESSAGE)
await self.__bot.change_presence(status=Status.online, activity=Game(name=f"Vulkan | {self.__config.BOT_PREFIX}help"))
print(self.__messages.STARTUP_COMPLETE_MESSAGE)
@commands.Cog.listener()
async def on_command_error(self, ctx, error):
if isinstance(error, MissingRequiredArgument):
embed = self.__embeds.MISSING_ARGUMENTS()
await ctx.send(embed=embed)
elif isinstance(error, CommandNotFound):
embed = self.__embeds.COMMAND_NOT_FOUND()
await ctx.send(embed=embed)
else:
print(f'DEVELOPER NOTE -> Command Error: {error}')
embed = self.__embeds.UNKNOWN_ERROR()
await ctx.send(embed=embed)
@commands.command(name="help", help=helper.HELP_HELP, description=helper.HELP_HELP_LONG, aliases=['h', 'ajuda'])
@command(name="help", help=helper.HELP_HELP, description=helper.HELP_HELP_LONG, aliases=['h', 'ajuda'])
async def help_msg(self, ctx, command_help=''):
if command_help != '':
for command in self.__bot.commands:
@ -97,10 +74,10 @@ class ControlCog(commands.Cog):
colour=self.__colors.BLUE
)
embedhelp.set_thumbnail(url=self.__bot.user.avatar_url)
embedhelp.set_thumbnail(url=self.__bot.user.avatar)
await ctx.send(embed=embedhelp)
@commands.command(name='invite', help=helper.HELP_INVITE, description=helper.HELP_INVITE_LONG, aliases=['convite', 'inv', 'convidar'])
@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)
txt = self.__config.INVITE_MESSAGE.format(invite_url, invite_url)

View File

@ -1,6 +1,4 @@
from discord import Guild, Client
from discord.ext import commands
from discord.ext.commands import Context
from discord.ext.commands import Context, command, Cog
from Config.Helper import Helper
from Handlers.ClearHandler import ClearHandler
from Handlers.MoveHandler import MoveHandler
@ -17,214 +15,218 @@ 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
from UI.Responses.EmoteCogResponse import EmoteCommandResponse
from UI.Responses.EmbedCogResponse import EmbedCommandResponse
from Music.VulkanBot import VulkanBot
from Config.Configs import VConfigs
from Parallelism.ProcessManager import ProcessManager
helper = Helper()
class MusicCog(commands.Cog):
class MusicCog(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
def __init__(self, bot: VulkanBot) -> None:
self.__bot: VulkanBot = bot
VConfigs().setProcessManager(ProcessManager(bot))
@commands.command(name="play", help=helper.HELP_PLAY, description=helper.HELP_PLAY_LONG, aliases=['p', 'tocar'])
@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)
view1 = EmbedCommandResponse(response)
view2 = EmoteCommandResponse(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'])
@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)
view2 = EmbedCommandResponse(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'])
@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)
view = EmoteCommandResponse(response)
else:
view = EmbedView(response)
view = EmbedCommandResponse(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'])
@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)
view = EmoteCommandResponse(response)
else:
view = EmbedView(response)
view = EmbedCommandResponse(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'])
@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)
view1 = EmoteCommandResponse(response)
view2 = EmbedCommandResponse(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'])
@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)
view1 = EmoteCommandResponse(response)
view2 = EmbedCommandResponse(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'])
@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)
view1 = EmbedCommandResponse(response)
view2 = EmoteCommandResponse(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'])
@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)
view1 = EmbedCommandResponse(response)
view2 = EmoteCommandResponse(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'])
@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)
view1 = EmoteCommandResponse(response)
view2 = EmbedCommandResponse(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'])
@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)
view = EmoteCommandResponse(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'])
@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)
view1 = EmbedCommandResponse(response)
view2 = EmoteCommandResponse(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'])
@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)
view1 = EmbedCommandResponse(response)
view2 = EmoteCommandResponse(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'])
@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)
view1 = EmbedCommandResponse(response)
view2 = EmoteCommandResponse(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'])
@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)
view1 = EmbedCommandResponse(response)
view2 = EmoteCommandResponse(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'])
@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)
view1 = EmbedCommandResponse(response)
view2 = EmoteCommandResponse(response)
await view1.run()
await view2.run()
except Exception as e:

View File

@ -1,8 +1,8 @@
from random import randint, random
from discord import Client
from Music.VulkanBot import VulkanBot
from discord.ext.commands import Context, command, Cog
from Config.Helper import Helper
from Views.Embeds import Embeds
from Config.Embeds import VEmbeds
helper = Helper()
@ -10,8 +10,8 @@ helper = Helper()
class RandomCog(Cog):
"""Class to listen to commands of type Random"""
def __init__(self, bot: Client):
self.__embeds = Embeds()
def __init__(self, bot: VulkanBot):
self.__embeds = VEmbeds()
@command(name='random', help=helper.HELP_RANDOM, description=helper.HELP_RANDOM_LONG, aliases=['rand'])
async def random(self, ctx: Context, arg: str) -> None:

View File

@ -1,26 +1,31 @@
from abc import ABC, abstractmethod
from typing import List
from typing import List, Union
from discord.ext.commands import Context
from discord import Client, Guild, ClientUser, Member
from discord import Client, Guild, ClientUser, Interaction, Member, User
from Config.Messages import Messages
from Music.VulkanBot import VulkanBot
from Handlers.HandlerResponse import HandlerResponse
from Config.Configs import Configs
from Config.Configs import VConfigs
from Config.Helper import Helper
from Views.Embeds import Embeds
from Config.Embeds import VEmbeds
class AbstractHandler(ABC):
def __init__(self, ctx: Context, bot: Client) -> None:
self.__bot: Client = bot
def __init__(self, ctx: Union[Context, Interaction], bot: VulkanBot) -> None:
self.__bot: VulkanBot = bot
self.__guild: Guild = ctx.guild
self.__ctx: Context = ctx
self.__bot_user: ClientUser = self.__bot.user
self.__id = self.__bot_user.id
self.__messages = Messages()
self.__config = Configs()
self.__config = VConfigs()
self.__helper = Helper()
self.__embeds = Embeds()
self.__embeds = VEmbeds()
self.__bot_member: Member = self.__get_member()
if isinstance(ctx, Context):
self.__author = ctx.author
else:
self.__author = ctx.user
@abstractmethod
async def run(self) -> HandlerResponse:
@ -38,6 +43,10 @@ class AbstractHandler(ABC):
def bot_user(self) -> ClientUser:
return self.__bot_user
@property
def author(self) -> User:
return self.__author
@property
def guild(self) -> Guild:
return self.__guild
@ -47,7 +56,7 @@ class AbstractHandler(ABC):
return self.__bot
@property
def config(self) -> Configs:
def config(self) -> VConfigs:
return self.__config
@property
@ -59,11 +68,11 @@ class AbstractHandler(ABC):
return self.__helper
@property
def ctx(self) -> Context:
def ctx(self) -> Union[Context, Interaction]:
return self.__ctx
@property
def embeds(self) -> Embeds:
def embeds(self) -> VEmbeds:
return self.__embeds
def __get_member(self) -> Member:

View File

@ -1,17 +1,18 @@
from typing import Union
from discord import Interaction
from discord.ext.commands import Context
from discord import Client
from Music.VulkanBot import VulkanBot
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:
def __init__(self, ctx: Union[Context, Interaction], bot: VulkanBot) -> None:
super().__init__(ctx, bot)
async def run(self) -> HandlerResponse:
# Get the current process of the guild
processManager = ProcessManager()
processManager = self.config.getProcessManager()
processInfo = processManager.getRunningPlayerInfo(self.guild)
if processInfo:
# Clear the playlist
@ -21,7 +22,6 @@ class ClearHandler(AbstractHandler):
if acquired:
playlist.clear()
processLock.release()
processLock.release()
return HandlerResponse(self.ctx)
else:
processManager.resetProcess(self.guild, self.ctx)

View File

@ -1,18 +1,18 @@
from typing import Union
from discord.ext.commands import Context
from Config.Exceptions import VulkanError
from discord import Embed
from discord import Embed, Interaction
class HandlerResponse:
def __init__(self, ctx: Context, embed: Embed = None, error: VulkanError = None) -> None:
def __init__(self, ctx: Union[Context, Interaction], embed: Embed = None, error: VulkanError = None) -> None:
self.__ctx: Context = ctx
self.__error: VulkanError = error
self.__embed: Embed = embed
self.__success = False if error else True
@property
def ctx(self) -> Context:
def ctx(self) -> Union[Context, Interaction]:
return self.__ctx
@property

View File

@ -1,18 +1,19 @@
from discord.ext.commands import Context
from discord import Client
from Music.VulkanBot import VulkanBot
from Handlers.AbstractHandler import AbstractHandler
from Handlers.HandlerResponse import HandlerResponse
from Utils.Utils import Utils
from Parallelism.ProcessManager import ProcessManager
from typing import Union
from discord import Interaction
class HistoryHandler(AbstractHandler):
def __init__(self, ctx: Context, bot: Client) -> None:
def __init__(self, ctx: Union[Context, Interaction], bot: VulkanBot) -> None:
super().__init__(ctx, bot)
async def run(self) -> HandlerResponse:
# Get the current process of the guild
processManager = ProcessManager()
processManager = self.config.getProcessManager()
processInfo = processManager.getRunningPlayerInfo(self.guild)
if processInfo:
processLock = processInfo.getLock()

View File

@ -1,18 +1,19 @@
from discord.ext.commands import Context
from discord import Client
from Music.VulkanBot import VulkanBot
from Handlers.AbstractHandler import AbstractHandler
from Handlers.HandlerResponse import HandlerResponse
from Config.Exceptions import BadCommandUsage
from Parallelism.ProcessManager import ProcessManager
from typing import Union
from discord import Interaction
class LoopHandler(AbstractHandler):
def __init__(self, ctx: Context, bot: Client) -> None:
def __init__(self, ctx: Union[Context, Interaction], bot: VulkanBot) -> None:
super().__init__(ctx, bot)
async def run(self, args: str) -> HandlerResponse:
# Get the current process of the guild
processManager = ProcessManager()
processManager = self.config.getProcessManager()
processInfo = processManager.getRunningPlayerInfo(self.guild)
if not processInfo:
embed = self.embeds.NOT_PLAYING()

View File

@ -1,19 +1,20 @@
from typing import Union
from discord.ext.commands import Context
from discord import Client
from Music.VulkanBot import VulkanBot
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
from typing import Union
from discord import Interaction
class MoveHandler(AbstractHandler):
def __init__(self, ctx: Context, bot: Client) -> None:
def __init__(self, ctx: Union[Context, Interaction], bot: VulkanBot) -> None:
super().__init__(ctx, bot)
async def run(self, pos1: str, pos2: str) -> HandlerResponse:
processManager = ProcessManager()
processManager = self.config.getProcessManager()
processInfo = processManager.getRunningPlayerInfo(self.guild)
if not processInfo:
embed = self.embeds.NOT_PLAYING()

View File

@ -1,19 +1,20 @@
from discord.ext.commands import Context
from discord import Client
from Handlers.AbstractHandler import AbstractHandler
from Handlers.HandlerResponse import HandlerResponse
from Music.VulkanBot import VulkanBot
from Utils.Cleaner import Cleaner
from Parallelism.ProcessManager import ProcessManager
from typing import Union
from discord import Interaction
class NowPlayingHandler(AbstractHandler):
def __init__(self, ctx: Context, bot: Client) -> None:
def __init__(self, ctx: Union[Context, Interaction], bot: VulkanBot) -> None:
super().__init__(ctx, bot)
self.__cleaner = Cleaner()
async def run(self) -> HandlerResponse:
# Get the current process of the guild
processManager = ProcessManager()
processManager = self.config.getProcessManager()
processInfo = processManager.getRunningPlayerInfo(self.guild)
if not processInfo:
embed = self.embeds.NOT_PLAYING()

View File

@ -1,22 +1,23 @@
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
from Music.VulkanBot import VulkanBot
from typing import Union
from discord import Interaction
class PauseHandler(AbstractHandler):
def __init__(self, ctx: Context, bot: Client) -> None:
def __init__(self, ctx: Union[Context, Interaction], bot: VulkanBot) -> None:
super().__init__(ctx, bot)
async def run(self) -> HandlerResponse:
processManager = ProcessManager()
processManager = self.config.getProcessManager()
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 = processInfo.getQueueToPlayer()
queue.put(command)
return HandlerResponse(self.ctx)

View File

@ -2,20 +2,21 @@ 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
from Music.VulkanBot import VulkanBot
from typing import Union
from discord import Interaction
class PlayHandler(AbstractHandler):
def __init__(self, ctx: Context, bot: Client) -> None:
def __init__(self, ctx: Union[Context, Interaction], bot: VulkanBot) -> None:
super().__init__(ctx, bot)
self.__searcher = Searcher()
self.__down = Downloader()
@ -36,8 +37,8 @@ class PlayHandler(AbstractHandler):
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)
processManager = self.config.getProcessManager()
processInfo = processManager.getOrCreatePlayerInfo(self.guild, self.ctx)
playlist = processInfo.getPlaylist()
process = processInfo.getProcess()
if not process.is_alive(): # If process has not yet started, start
@ -72,7 +73,7 @@ class PlayHandler(AbstractHandler):
playlist.add_song(song)
# Release the acquired Lock
processLock.release()
queue = processInfo.getQueue()
queue = processInfo.getQueueToPlayer()
playCommand = VCommands(VCommandsType.PLAY, None)
queue.put(playCommand)
else:
@ -104,7 +105,7 @@ class PlayHandler(AbstractHandler):
async def __downloadSongsAndStore(self, songs: List[Song], processInfo: ProcessInfo) -> None:
playlist = processInfo.getPlaylist()
queue = processInfo.getQueue()
queue = processInfo.getQueueToPlayer()
playCommand = VCommands(VCommandsType.PLAY, None)
# Trigger a task for each song to be downloaded
tasks: List[asyncio.Task] = []
@ -113,12 +114,12 @@ class PlayHandler(AbstractHandler):
tasks.append(task)
# In the original order, await for the task and then if successfully downloaded add in the playlist
processManager = ProcessManager()
processManager = self.config.getProcessManager()
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)
processInfo = processManager.getOrCreatePlayerInfo(self.guild, self.ctx)
processLock = processInfo.getLock()
acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
if acquired:

View File

@ -1,19 +1,25 @@
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
from Music.VulkanBot import VulkanBot
from typing import Union
from discord import Interaction
class PrevHandler(AbstractHandler):
def __init__(self, ctx: Context, bot: Client) -> None:
def __init__(self, ctx: Union[Context, Interaction], bot: VulkanBot) -> None:
super().__init__(ctx, bot)
async def run(self) -> HandlerResponse:
processManager = ProcessManager()
processInfo = processManager.getPlayerInfo(self.guild, self.ctx)
if not self.__user_connected():
error = ImpossibleMove()
embed = self.embeds.NO_CHANNEL()
return HandlerResponse(self.ctx, embed, error)
processManager = self.config.getProcessManager()
processInfo = processManager.getOrCreatePlayerInfo(self.guild, self.ctx)
if not processInfo:
embed = self.embeds.NOT_PLAYING()
error = BadCommandUsage()
@ -25,11 +31,6 @@ class PrevHandler(AbstractHandler):
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()
@ -41,13 +42,13 @@ class PrevHandler(AbstractHandler):
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()
prevCommand = VCommands(VCommandsType.PREV, self.author.voice.channel.id)
queue = processInfo.getQueueToPlayer()
queue.put(prevCommand)
return HandlerResponse(self.ctx)
def __user_connected(self) -> bool:
if self.ctx.author.voice:
if self.author.voice:
return True
else:
return False

View File

@ -1,20 +1,21 @@
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
from Music.VulkanBot import VulkanBot
from typing import Union
from discord import Interaction
class QueueHandler(AbstractHandler):
def __init__(self, ctx: Context, bot: Client) -> None:
def __init__(self, ctx: Union[Context, Interaction], bot: VulkanBot) -> None:
super().__init__(ctx, bot)
self.__down = Downloader()
async def run(self) -> HandlerResponse:
# Retrieve the process of the guild
processManager = ProcessManager()
processManager = self.config.getProcessManager()
processInfo = processManager.getRunningPlayerInfo(self.guild)
if not processInfo: # If no process return empty list
embed = self.embeds.EMPTY_QUEUE()

View File

@ -1,20 +1,21 @@
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
from Music.VulkanBot import VulkanBot
from typing import Union
from discord import Interaction
class RemoveHandler(AbstractHandler):
def __init__(self, ctx: Context, bot: Client) -> None:
def __init__(self, ctx: Union[Context, Interaction], bot: VulkanBot) -> None:
super().__init__(ctx, bot)
async def run(self, position: str) -> HandlerResponse:
# Get the current process of the guild
processManager = ProcessManager()
processManager = self.config.getProcessManager()
processInfo = processManager.getRunningPlayerInfo(self.guild)
if not processInfo:
# Clear the playlist

View File

@ -1,22 +1,23 @@
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
from Music.VulkanBot import VulkanBot
from typing import Union
from discord import Interaction
class ResetHandler(AbstractHandler):
def __init__(self, ctx: Context, bot: Client) -> None:
def __init__(self, ctx: Union[Context, Interaction], bot: VulkanBot) -> None:
super().__init__(ctx, bot)
async def run(self) -> HandlerResponse:
# Get the current process of the guild
processManager = ProcessManager()
processManager = self.config.getProcessManager()
processInfo = processManager.getRunningPlayerInfo(self.guild)
if processInfo:
command = VCommands(VCommandsType.RESET, None)
queue = processInfo.getQueue()
queue = processInfo.getQueueToPlayer()
queue.put(command)
return HandlerResponse(self.ctx)

View File

@ -1,22 +1,23 @@
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
from Music.VulkanBot import VulkanBot
from typing import Union
from discord import Interaction
class ResumeHandler(AbstractHandler):
def __init__(self, ctx: Context, bot: Client) -> None:
def __init__(self, ctx: Union[Context, Interaction], bot: VulkanBot) -> None:
super().__init__(ctx, bot)
async def run(self) -> HandlerResponse:
processManager = ProcessManager()
processManager = self.config.getProcessManager()
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 = processInfo.getQueueToPlayer()
queue.put(command)
return HandlerResponse(self.ctx)

View File

@ -1,17 +1,18 @@
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
from Music.VulkanBot import VulkanBot
from typing import Union
from discord import Interaction
class ShuffleHandler(AbstractHandler):
def __init__(self, ctx: Context, bot: Client) -> None:
def __init__(self, ctx: Union[Context, Interaction], bot: VulkanBot) -> None:
super().__init__(ctx, bot)
async def run(self) -> HandlerResponse:
processManager = ProcessManager()
processManager = self.config.getProcessManager()
processInfo = processManager.getRunningPlayerInfo(self.guild)
if processInfo:
try:

View File

@ -1,18 +1,24 @@
from discord.ext.commands import Context
from discord import Client
from Handlers.AbstractHandler import AbstractHandler
from Config.Exceptions import BadCommandUsage
from Config.Exceptions import BadCommandUsage, ImpossibleMove
from Handlers.HandlerResponse import HandlerResponse
from Parallelism.ProcessManager import ProcessManager
from Music.VulkanBot import VulkanBot
from Parallelism.Commands import VCommands, VCommandsType
from typing import Union
from discord import Interaction
class SkipHandler(AbstractHandler):
def __init__(self, ctx: Context, bot: Client) -> None:
def __init__(self, ctx: Union[Context, Interaction], bot: VulkanBot) -> None:
super().__init__(ctx, bot)
async def run(self) -> HandlerResponse:
processManager = ProcessManager()
if not self.__user_connected():
error = ImpossibleMove()
embed = self.embeds.NO_CHANNEL()
return HandlerResponse(self.ctx, embed, error)
processManager = self.config.getProcessManager()
processInfo = processManager.getRunningPlayerInfo(self.guild)
if processInfo: # Verify if there is a running process
playlist = processInfo.getPlaylist()
@ -23,10 +29,16 @@ class SkipHandler(AbstractHandler):
# Send a command to the player process to skip the music
command = VCommands(VCommandsType.SKIP, None)
queue = processInfo.getQueue()
queue = processInfo.getQueueToPlayer()
queue.put(command)
return HandlerResponse(self.ctx)
else:
embed = self.embeds.NOT_PLAYING()
return HandlerResponse(self.ctx, embed)
def __user_connected(self) -> bool:
if self.author.voice:
return True
else:
return False

View File

@ -1,22 +1,23 @@
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 Music.VulkanBot import VulkanBot
from Parallelism.Commands import VCommands, VCommandsType
from typing import Union
from discord import Interaction
class StopHandler(AbstractHandler):
def __init__(self, ctx: Context, bot: Client) -> None:
def __init__(self, ctx: Union[Context, Interaction], bot: VulkanBot) -> None:
super().__init__(ctx, bot)
async def run(self) -> HandlerResponse:
processManager = ProcessManager()
processManager = self.config.getProcessManager()
processInfo = processManager.getRunningPlayerInfo(self.guild)
if processInfo:
# Send command to player process stop
command = VCommands(VCommandsType.STOP, None)
queue = processInfo.getQueue()
queue = processInfo.getQueueToPlayer()
queue.put(command)
return HandlerResponse(self.ctx)

View File

@ -1,6 +1,6 @@
import asyncio
from typing import List
from Config.Configs import Configs
from Config.Configs import VConfigs
from yt_dlp import YoutubeDL, DownloadError
from concurrent.futures import ThreadPoolExecutor
from Music.Song import Song
@ -9,7 +9,7 @@ from Config.Exceptions import DownloadingError
class Downloader:
config = Configs()
config = VConfigs()
__YDL_OPTIONS = {'format': 'bestaudio/best',
'default_search': 'auto',
'playliststart': 0,
@ -34,7 +34,7 @@ class Downloader:
__BASE_URL = 'https://www.youtube.com/watch?v={}'
def __init__(self) -> None:
self.__config = Configs()
self.__config = VConfigs()
self.__music_keys_only = ['resolution', 'fps', 'quality']
self.__not_extracted_keys_only = ['ie_key']
self.__not_extracted_not_keys = ['entries']

View File

@ -0,0 +1,65 @@
from typing import List
from discord import Embed, Message, TextChannel
from Music.VulkanBot import VulkanBot
from Parallelism.ProcessInfo import ProcessInfo
from Config.Configs import VConfigs
from Config.Messages import Messages
from Music.Song import Song
from Config.Embeds import VEmbeds
from UI.Views.PlayerView import PlayerView
class MessagesController:
def __init__(self, bot: VulkanBot) -> None:
self.__bot = bot
self.__previousMessages = []
self.__configs = VConfigs()
self.__messages = Messages()
self.__embeds = VEmbeds()
async def sendNowPlaying(self, processInfo: ProcessInfo, song: Song) -> None:
# Get the lock of the playlist
playlist = processInfo.getPlaylist()
if playlist.isLoopingOne():
title = self.__messages.ONE_SONG_LOOPING
else:
title = self.__messages.SONG_PLAYING
# Create View and Embed
embed = self.__embeds.SONG_INFO(song.info, title)
view = PlayerView(self.__bot)
channel = processInfo.getTextChannel()
# Delete the previous and send the message
await self.__deletePreviousNPMessages()
await channel.send(embed=embed, view=view)
# Get the sended message
sendedMessage = await self.__getSendedMessage(channel)
# Set the message witch contains the view
view.set_message(message=sendedMessage)
self.__previousMessages.append(sendedMessage)
async def __deletePreviousNPMessages(self) -> None:
for message in self.__previousMessages:
try:
await message.delete()
except:
pass
self.__previousMessages.clear()
async def __getSendedMessage(self, channel: TextChannel) -> Message:
stringToIdentify = 'Uploader:'
last_messages: List[Message] = await channel.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

@ -1,6 +1,6 @@
from collections import deque
from typing import List
from Config.Configs import Configs
from Config.Configs import VConfigs
from Music.Song import Song
import random
@ -8,7 +8,7 @@ import random
class Playlist:
def __init__(self) -> None:
self.__configs = Configs()
self.__configs = VConfigs()
self.__queue = deque() # Store the musics to play
self.__songs_history = deque() # Store the musics played
@ -62,7 +62,7 @@ class Playlist:
# Att played song info
if played_song != None:
if not self.__looping_one and not self.__looping_all:
if played_song.problematic == False:
if not played_song.problematic:
self.__songs_history.appendleft(played_song)
if len(self.__songs_history) > self.__configs.MAX_SONGS_HISTORY:

View File

@ -2,14 +2,14 @@ from spotipy import Spotify
from spotipy.oauth2 import SpotifyClientCredentials
from spotipy.exceptions import SpotifyException
from Config.Exceptions import SpotifyError
from Config.Configs import Configs
from Config.Configs import VConfigs
from Config.Messages import SpotifyMessages
class SpotifySearch():
def __init__(self) -> None:
self.__messages = SpotifyMessages()
self.__config = Configs()
self.__config = VConfigs()
self.__connected = False
self.__connect()

73
Music/VulkanBot.py Normal file
View File

@ -0,0 +1,73 @@
from asyncio import AbstractEventLoop
from discord import Guild, Status, Game, Message
from discord.ext.commands.errors import CommandNotFound, MissingRequiredArgument
from Config.Configs import VConfigs
from discord.ext.commands import Bot, Context
from Config.Messages import Messages
from Config.Embeds import VEmbeds
class VulkanBot(Bot):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__configs = VConfigs()
self.__messages = Messages()
self.__embeds = VEmbeds()
self.remove_command("help")
def startBot(self) -> None:
"""Blocking function that will start the bot"""
if self.__configs.BOT_TOKEN == '':
print('DEVELOPER NOTE -> Token not found')
exit()
super().run(self.__configs.BOT_TOKEN, reconnect=True)
async def startBotCoro(self, loop: AbstractEventLoop) -> None:
"""Start a bot coroutine, does not wait for connection to be established"""
task = loop.create_task(self.__login())
await task
loop.create_task(self.__connect())
async def __login(self):
"""Coroutine to login the Bot in discord"""
await self.login(token=self.__configs.BOT_TOKEN)
async def __connect(self):
"""Coroutine to connect the Bot in discord"""
await self.connect(reconnect=True)
async def on_ready(self):
print(self.__messages.STARTUP_MESSAGE)
await self.change_presence(status=Status.online, activity=Game(name=f"Vulkan | {self.__configs.BOT_PREFIX}help"))
print(self.__messages.STARTUP_COMPLETE_MESSAGE)
async def on_command_error(self, ctx, error):
if isinstance(error, MissingRequiredArgument):
embed = self.__embeds.MISSING_ARGUMENTS()
await ctx.send(embed=embed)
elif isinstance(error, CommandNotFound):
embed = self.__embeds.COMMAND_NOT_FOUND()
await ctx.send(embed=embed)
else:
print(f'DEVELOPER NOTE -> Command Error: {error}')
embed = self.__embeds.UNKNOWN_ERROR()
await ctx.send(embed=embed)
async def process_commands(self, message: Message):
if message.author.bot:
return
ctx = await self.get_context(message, cls=Context)
if ctx.valid and not message.guild:
return
await self.invoke(ctx)
class Context(Context):
bot: VulkanBot
guild: Guild

View File

@ -0,0 +1,55 @@
from random import choices
import string
from discord.bot import Bot
from discord import Intents
from Music.VulkanBot import VulkanBot
from os import listdir
from Config.Configs import VConfigs
from Config.Exceptions import VulkanError
class VulkanInitializer:
def __init__(self, willListen: bool) -> None:
self.__config = VConfigs()
self.__intents = Intents.default()
self.__intents.message_content = True
self.__intents.members = True
self.__bot = self.__create_bot(willListen)
self.__add_cogs(self.__bot)
def getBot(self) -> VulkanBot:
return self.__bot
def __create_bot(self, willListen: bool) -> VulkanBot:
if willListen:
prefix = self.__config.BOT_PREFIX
else:
prefix = ''.join(choices(string.ascii_uppercase + string.digits, k=4))
bot = VulkanBot(command_prefix=prefix,
pm_help=True,
case_insensitive=True,
intents=self.__intents)
return bot
def __add_cogs(self, bot: Bot) -> None:
try:
cogsStatus = []
for filename in listdir(f'./{self.__config.COMMANDS_PATH}'):
if filename.endswith('.py'):
cogPath = f'{self.__config.COMMANDS_PATH}.{filename[:-3]}'
cogsStatus.append(bot.load_extension(cogPath, store=True))
if len(bot.cogs.keys()) != self.__getTotalCogs():
print(cogsStatus)
raise VulkanError(message='Failed to load some Cog')
except VulkanError as e:
print(f'[Error Loading Vulkan] -> {e.message}')
def __getTotalCogs(self) -> int:
quant = 0
for filename in listdir(f'./{self.__config.COMMANDS_PATH}'):
if filename.endswith('.py'):
quant += 1
return quant

View File

@ -11,6 +11,9 @@ class VCommandsType(Enum):
PLAY = 'Play'
STOP = 'Stop'
RESET = 'Reset'
NOW_PLAYING = 'Now Playing'
TERMINATE = 'Terminate'
SLEEPING = 'Sleeping'
class VCommands:

View File

@ -1,17 +1,17 @@
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 Music.VulkanInitializer import VulkanInitializer
from discord import User, Member, Message
from asyncio import AbstractEventLoop, Semaphore, Queue
from multiprocessing import Process, RLock, Lock, Queue
from threading import Thread
from typing import Callable, List
from discord import Client, Guild, FFmpegPCMAudio, VoiceChannel, TextChannel
from discord import Guild, FFmpegPCMAudio, VoiceChannel, TextChannel
from Music.Playlist import Playlist
from Music.Song import Song
from Config.Configs import Configs
from Config.Configs import VConfigs
from Config.Messages import Messages
from discord.ext.commands import Bot
from Views.Embeds import Embeds
from Music.VulkanBot import VulkanBot
from Config.Embeds import VEmbeds
from Parallelism.Commands import VCommands, VCommandsType
@ -21,7 +21,7 @@ class TimeoutClock:
self.__task = loop.create_task(self.__executor())
async def __executor(self):
await asyncio.sleep(Configs().VC_TIMEOUT)
await asyncio.sleep(VConfigs().VC_TIMEOUT)
await self.__callback()
def cancel(self):
@ -31,7 +31,7 @@ class TimeoutClock:
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:
def __init__(self, name: str, playlist: Playlist, lock: Lock, queueToReceive: Queue, queueToSend: 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
@ -40,7 +40,8 @@ class PlayerProcess(Process):
# Synchronization objects
self.__playlist: Playlist = playlist
self.__playlistLock: Lock = lock
self.__queue: Queue = queue
self.__queueReceive: Queue = queueToReceive
self.__queueSend: Queue = queueToSend
self.__semStopPlaying: Semaphore = None
self.__loop: AbstractEventLoop = None
# Discord context ID
@ -50,14 +51,14 @@ class PlayerProcess(Process):
self.__authorID = authorID
# All information of discord context will be retrieved directly with discord API
self.__guild: Guild = None
self.__bot: Client = None
self.__bot: VulkanBot = 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.__configs: VConfigs = None
self.__embeds: VEmbeds = None
self.__messages: Messages = None
self.__messagesToDelete: List[Message] = []
self.__playing = False
@ -70,11 +71,12 @@ class PlayerProcess(Process):
try:
print(f'Starting Process {self.name}')
self.__playerLock = RLock()
self.__loop = asyncio.get_event_loop()
self.__loop = asyncio.get_event_loop_policy().new_event_loop()
asyncio.set_event_loop(self.__loop)
self.__configs = Configs()
self.__configs = VConfigs()
self.__messages = Messages()
self.__embeds = Embeds()
self.__embeds = VEmbeds()
self.__semStopPlaying = Semaphore(0)
self.__loop.run_until_complete(self._run())
@ -107,8 +109,12 @@ class PlayerProcess(Process):
self.__timer.cancel()
async def __playPlaylistSongs(self) -> None:
"""If the player is not running trigger to play a new song"""
if not self.__playing:
song = None
with self.__playlistLock:
with self.__playerLock:
if not (self.__guild.voice_client.is_playing() or self.__guild.voice_client.is_paused()):
song = self.__playlist.next_song()
if song is not None:
@ -141,8 +147,8 @@ class PlayerProcess(Process):
self.__timer.cancel()
self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop)
await self.__deletePrevNowPlaying()
await self.__showNowPlaying()
nowPlayingCommand = VCommands(VCommandsType.NOW_PLAYING, song)
self.__queueSend.put(nowPlayingCommand)
except Exception as e:
print(f'[ERROR IN PLAY SONG] -> {e}, {type(e)}')
self.__playNext(None)
@ -150,26 +156,31 @@ class PlayerProcess(Process):
self.__playerLock.release()
def __playNext(self, error) -> None:
with self.__playlistLock:
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
# Send a command to the main process put this one to sleep
sleepCommand = VCommands(VCommandsType.SLEEPING)
self.__queueSend.put(sleepCommand)
# Release the semaphore to finish the process
self.__semStopPlaying.release()
async def __playPrev(self, voiceChannelID: int) -> None:
with self.__playlistLock:
song = self.__playlist.prev_song()
with self.__playerLock:
if song is not None:
if self.__guild.voice_client is None: # If not connect, connect to the user voice channel
self.__voiceChannelID = voiceChannelID
@ -187,7 +198,7 @@ class PlayerProcess(Process):
def __commandsReceiver(self) -> None:
while True:
command: VCommands = self.__queue.get()
command: VCommands = self.__queueReceive.get()
type = command.getType()
args = command.getArgs()
@ -235,9 +246,11 @@ class PlayerProcess(Process):
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()
# Send a command to the main process put this to sleep
sleepCommand = VCommands(VCommandsType.SLEEPING)
self.__queueSend.put(sleepCommand)
self.__guild.voice_client.stop()
self.__playingSong = None
await self.__guild.voice_client.disconnect()
@ -269,30 +282,13 @@ class PlayerProcess(Process):
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')
async def __createBotInstance(self) -> VulkanBot:
"""Load a new bot instance that should not be directly called."""
initializer = VulkanInitializer(willListen=False)
bot = initializer.getBot()
# 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 bot.startBotCoro(self.__loop)
await self.__ensureDiscordConnection(bot)
return bot
async def __timeoutHandler(self) -> None:
@ -301,21 +297,36 @@ class PlayerProcess(Process):
return
if self.__guild.voice_client.is_playing() or self.__guild.voice_client.is_paused():
if not self.__isBotAloneInChannel(): # If bot is not alone continue to play
self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop)
return
elif self.__guild.voice_client.is_connected():
# Finish the process
if 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()
# Send command to main process to finish this one
sleepCommand = VCommands(VCommandsType.SLEEPING)
self.__queueSend.put(sleepCommand)
# 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:
def __isBotAloneInChannel(self) -> bool:
try:
if len(self.__guild.voice_client.channel.members) <= 1:
return True
else:
return False
except Exception as e:
print(f'[ERROR IN CHECK BOT ALONE] -> {e}')
return False
async def __ensureDiscordConnection(self, bot: VulkanBot) -> None:
"""Await in this point until connection to discord is established"""
guild = None
while guild is None:
@ -335,46 +346,3 @@ class PlayerProcess(Process):
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

@ -1,4 +1,5 @@
from multiprocessing import Process, Queue, Lock
from discord import TextChannel
from Music.Playlist import Playlist
@ -7,11 +8,13 @@ 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:
def __init__(self, process: Process, queueToPlayer: Queue, queueToMain: Queue, playlist: Playlist, lock: Lock, textChannel: TextChannel) -> None:
self.__process = process
self.__queue = queue
self.__queueToPlayer = queueToPlayer
self.__queueToMain = queueToMain
self.__playlist = playlist
self.__lock = lock
self.__textChannel = textChannel
def setProcess(self, newProcess: Process) -> None:
self.__process = newProcess
@ -19,11 +22,17 @@ class ProcessInfo:
def getProcess(self) -> Process:
return self.__process
def getQueue(self) -> Queue:
return self.__queue
def getQueueToPlayer(self) -> Queue:
return self.__queueToPlayer
def getQueueToMain(self) -> Queue:
return self.__queueToMain
def getPlaylist(self) -> Playlist:
return self.__playlist
def getLock(self) -> Lock:
return self.__lock
def getTextChannel(self) -> TextChannel:
return self.__textChannel

View File

@ -1,13 +1,19 @@
from multiprocessing import Queue, Lock
import asyncio
from multiprocessing import Lock, Queue
from multiprocessing.managers import BaseManager, NamespaceProxy
from typing import Dict
from queue import Empty
from threading import Thread
from typing import Dict, Tuple, Union
from Config.Singleton import Singleton
from discord import Guild
from discord import Guild, Interaction
from discord.ext.commands import Context
from Music.MessagesController import MessagesController
from Music.Song import Song
from Parallelism.PlayerProcess import PlayerProcess
from Music.Playlist import Playlist
from Parallelism.ProcessInfo import ProcessInfo
from Parallelism.Commands import VCommands, VCommandsType
from Music.VulkanBot import VulkanBot
class ProcessManager(Singleton):
@ -16,25 +22,28 @@ class ProcessManager(Singleton):
Deal with the creation of shared memory
"""
def __init__(self) -> None:
def __init__(self, bot: VulkanBot = None) -> None:
if not super().created:
self.__bot = bot
VManager.register('Playlist', Playlist)
self.__manager = VManager()
self.__manager.start()
self.__playersProcess: Dict[Guild, ProcessInfo] = {}
self.__playersListeners: Dict[Guild, Tuple[Thread, bool]] = {}
self.__playersMessages: Dict[Guild, MessagesController] = {}
def setPlayerContext(self, guild: Guild, context: ProcessInfo):
self.__playersProcess[guild.id] = context
def setPlayerInfo(self, guild: Guild, info: ProcessInfo):
self.__playersProcess[guild.id] = info
def getPlayerInfo(self, guild: Guild, context: Context) -> ProcessInfo:
"""Return the process info for the guild, if not, create one"""
def getOrCreatePlayerInfo(self, guild: Guild, context: Union[Context, Interaction]) -> ProcessInfo:
"""Return the process info for the guild, the user in context must be connected to a voice_channel"""
try:
if guild.id not in self.__playersProcess.keys():
self.__playersProcess[guild.id] = self.__createProcessInfo(context)
self.__playersProcess[guild.id] = self.__createProcessInfo(guild, 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)
self.__playersProcess[guild.id] = self.__recreateProcess(guild, context)
return self.__playersProcess[guild.id]
except Exception as e:
@ -46,11 +55,11 @@ class ProcessManager(Singleton):
return None
# Recreate the process keeping the playlist
newProcessInfo = self.__recreateProcess(context)
newProcessInfo = self.__recreateProcess(guild, context)
newProcessInfo.getProcess().start() # Start the process
# Send a command to start the play again
playCommand = VCommands(VCommandsType.PLAY)
newProcessInfo.getQueue().put(playCommand)
newProcessInfo.getQueueToPlayer().put(playCommand)
self.__playersProcess[guild.id] = newProcessInfo
def getRunningPlayerInfo(self, guild: Guild) -> ProcessInfo:
@ -60,7 +69,7 @@ class ProcessManager(Singleton):
return self.__playersProcess[guild.id]
def __createProcessInfo(self, context: Context) -> ProcessInfo:
def __createProcessInfo(self, guild: Guild, context: Context) -> ProcessInfo:
guildID: int = context.guild.id
textID: int = context.channel.id
voiceID: int = context.author.voice.channel.id
@ -68,30 +77,105 @@ class ProcessManager(Singleton):
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)
queueToListen = Queue()
queueToSend = Queue()
process = PlayerProcess(context.guild.name, playlist, lock, queueToSend,
queueToListen, guildID, textID, voiceID, authorID)
processInfo = ProcessInfo(process, queueToSend, queueToListen,
playlist, lock, context.channel)
# Create a Thread to listen for the queue coming from the Player Process, this will redirect the Queue to a async
thread = Thread(target=self.__listenToCommands,
args=(queueToListen, guild), daemon=True)
self.__playersListeners[guildID] = (thread, False)
thread.start()
# Create a Message Controller for this player
self.__playersMessages[guildID] = MessagesController(self.__bot)
return processInfo
def __recreateProcess(self, context: Context) -> ProcessInfo:
def __recreateProcess(self, guild: Guild, context: Union[Context, Interaction]) -> 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
if isinstance(context, Interaction):
authorID: int = context.user.id
voiceID: int = context.user.voice.channel.id
else:
authorID: int = context.author.id
voiceID: int = context.author.voice.channel.id
playlist: Playlist = self.__playersProcess[guildID].getPlaylist()
lock = Lock()
queue = Queue()
queueToListen = Queue()
queueToSend = Queue()
process = PlayerProcess(context.guild.name, playlist, lock, queueToSend,
queueToListen, guildID, textID, voiceID, authorID)
processInfo = ProcessInfo(process, queueToSend, queueToListen,
playlist, lock, context.channel)
process = PlayerProcess(context.guild.name, playlist, lock, queue,
guildID, textID, voiceID, authorID)
processInfo = ProcessInfo(process, queue, playlist, lock)
# Create a Thread to listen for the queue coming from the Player Process, this will redirect the Queue to a async
thread = Thread(target=self.__listenToCommands,
args=(queueToListen, guild), daemon=True)
self.__playersListeners[guildID] = (thread, False)
thread.start()
return processInfo
def __listenToCommands(self, queue: Queue, guild: Guild) -> None:
guildID = guild.id
while True:
shouldEnd = self.__playersListeners[guildID][1]
if shouldEnd:
break
try:
command: VCommands = queue.get(timeout=5)
commandType = command.getType()
args = command.getArgs()
print(f'Process {guild.name} sended command {commandType}')
if commandType == VCommandsType.NOW_PLAYING:
asyncio.run_coroutine_threadsafe(self.showNowPlaying(
guild.id, args), self.__bot.loop)
elif commandType == VCommandsType.TERMINATE:
# Delete the process elements and return, to finish task
self.__terminateProcess(guildID)
return
elif commandType == VCommandsType.SLEEPING:
# The process might be used again
self.__sleepingProcess(guildID)
return
else:
print(f'[ERROR] -> Unknown Command Received from Process: {commandType}')
except Empty:
continue
except Exception as e:
print(f'[ERROR IN LISTENING PROCESS] -> {guild.name} - {e}')
def __terminateProcess(self, guildID: int) -> None:
# Delete all structures associated with the Player
del self.__playersProcess[guildID]
del self.__playersMessages[guildID]
threadListening = self.__playersListeners[guildID]
threadListening._stop()
del self.__playersListeners[guildID]
def __sleepingProcess(self, guildID: int) -> None:
# Disable all process structures, except Playlist
queue1 = self.__playersProcess[guildID].getQueueToMain()
queue2 = self.__playersProcess[guildID].getQueueToPlayer()
queue1.close()
queue1.join_thread()
queue2.close()
queue2.join_thread()
async def showNowPlaying(self, guildID: int, song: Song) -> None:
messagesController = self.__playersMessages[guildID]
processInfo = self.__playersProcess[guildID]
await messagesController.sendNowPlaying(processInfo, song)
class VManager(BaseManager):
pass

View File

@ -1,68 +1,48 @@
# **Vulkan**
A Music Discord bot, written in Python, that plays *Youtube*, *Spotify* and *Deezer* links. Vulkan was designed so that anyone can fork this project, follow the instructions and use it in their own way, Vulkan can also be configured in Heroku to work 24/7.
<h1 align="center"> Vulkan</h1>
# **Music**
- Play musics from Youtube, Spotify and Deezer links (Albums, Artists, Playlists and Tracks)
- Control loop of one or all musics
- Allow moving and removing musics in the queue
- Play musics in queue randomly
- Store played songs and allow bidirectional flow
A Music Discord Bot, that plays *Youtube*, *Spotify*, *Deezer* links or raw queries. Vulkan is open source, so everyone can fork this project, follow the instructions and use it in their own way, executing it in your own machine or hosting in others machines to work 24/7.
### Commands
```!play [title, spotify_url, youtube_url, deezer_url]``` - Start playing song
Vulkan uses multiprocessing and asynchronous Python modules to maximize Music Player response time, so the player doesn't lag when many commands are being processed and it can play in multiples discord serves at the same time without affecting the Music Player response time.
```!resume``` - Resume the song player
```!pause``` - Pause the song player
<p align="center">
<img src="./Assets/playermenu.jfif" />
</p>
```!skip``` - Skip the currently playing song
```!prev``` - Return to play the previous song
```!stop``` - Stop the playing of musics
```!queue``` - Show the musics list in queue
```!history``` - Show the played songs list
```!loop [one, all, off]``` - Control the loop of songs
```!shuffle``` - Shuffle the songs in queue
```!remove [x]``` - Remove the song in position x
```!move [x, y]``` - Change the musics in position x and y in Queue
```!np``` - Show information of the currently song
```!clear``` - Clear the songs in queue, doesn't stop the player
```!reset``` - Reset the player, recommended if any error happen
```!invite``` - Send the URL to invite Vulkan to your server
```!help [command]``` - Show more info about the command selected
# **Music 🎧**
- Play musics from Youtube, Spotify and Deezer links (Albums, Artists, Playlists and Tracks).
- Play musics in multiple discord server at the same time.
- The player contain buttons to shortcut some commands.
- Manage the loop of one or all playing musics.
- Manage the order and remove musics from the queue.
- Shuffle the musics queue order.
# **Usage:**
<p align="center">
<img src="./Assets/vulkancommands.jfif" />
</p>
### **API Keys**
# **How to use it**
### **Requirements**
Installation of Python 3.8+ and the dependencies in the requirements.txt file, creation of your own Bot in Discord and Spotify Keys.
```
pip install -r requirements.txt
```
### **🔑 API Keys**
You have to create your own discord Bot and store your Bot Token
* Your Discord Application - [Discord](https://discord.com/developers)
* You own Spotify Keys - [Spotify](https://developer.spotify.com/dashboard/applications)
- This information must be stored in an .env file, explained further.
### **Requirements**
- Installation of Python 3.8+ and the dependencies in the requirements.txt file.
```
pip install -r requirements.txt
```
- **Installation of FFMPEG**<br>
### **Installation of FFMPEG**<br>
FFMPEG is a module that will be used to play music, you must have this configured in your machine
*FFMPEG must be configured in the PATH for Windows users. Check this [YoutubeVideo](https://www.youtube.com/watch?v=r1AtmY-RMyQ&t=114s&ab_channel=TroubleChute).* <br><br>
You can download the executables in this link `https://www.ffmpeg.org/download.html` and then put the .exe files inside a ffmpeg\bin folder in your C:\ folder. Do not forget to add 'ffmpeg\bin' to your PATH.
@ -78,8 +58,8 @@ BOT_PREFIX=Your_Wanted_Prefix_For_Vulkan
```
### **Config File**
The config file, located in ```./config``` folder doesn't require any change, but if you acquire the knowledged of how it works, you can change it to the way you want.
### **⚙️ Configs**
The config file is located at ```./config/Configs.py```, it doesn't require any change, but if you can change values to the way you want.
### **Initialization**
@ -87,25 +67,22 @@ The config file, located in ```./config``` folder doesn't require any change, bu
- Run ```python main.py``` in console to start
## **Heroku**
To run your Bot in Heroku 24/7, you will need the Procfile located in root, then follow the instructions in this [video](https://www.youtube.com/watch?v=BPvg9bndP1U&ab_channel=TechWithTim). In addition, also add these two buildpacks to your Heroku Application:
## **🚀 Heroku**
To deploy and run your Bot in Heroku 24/7, you will need the Procfile located in root, then follow the instructions in this [video](https://www.youtube.com/watch?v=BPvg9bndP1U&ab_channel=TechWithTim). In addition, also add these two buildpacks to your Heroku Application:
- https://github.com/jonathanong/heroku-buildpack-ffmpeg-latest.git
- https://github.com/xrisk/heroku-opus.git
## Testing
## 🧪 Tests
The tests were written manually with no package due to problems with async function in other packages, to execute them type in root: <br>
`python run_tests.py`<br>
## License
## 📖 License
- This program is free software: you can redistribute it and/or modify it under the terms of the [MIT License](https://github.com/RafaelSolVargas/Vulkan/blob/master/LICENSE).
## Contributing
## 🏗️ Contributing
- If you are interested in upgrading this project i will be very happy to receive a PR or Issue from you. See TODO project to see if i'm working in some feature now.
## Acknowledgment
- See the DingoLingo [project](https://github.com/Raptor123471/DingoLingo) from Raptor123471, it helped me a lot to build Vulkan.

22
UI/Buttons/BackButton.py Normal file
View File

@ -0,0 +1,22 @@
from discord import ButtonStyle, Interaction
from discord.ui import Button
from Config.Emojis import VEmojis
from Handlers.PrevHandler import PrevHandler
from Music.VulkanBot import VulkanBot
class BackButton(Button):
def __init__(self, bot: VulkanBot):
super().__init__(label="Back", style=ButtonStyle.secondary, emoji=VEmojis().BACK)
self.__bot = bot
async def callback(self, interaction: Interaction) -> None:
"""Callback to when Button is clicked"""
# Return to Discord that this command is being processed
await interaction.response.defer()
handler = PrevHandler(interaction, self.__bot)
response = await handler.run()
if response.embed:
await interaction.followup.send(embed=response.embed)

View File

@ -0,0 +1,20 @@
from discord import ButtonStyle, Interaction
from discord.ui import Button
from Config.Emojis import VEmojis
from Handlers.LoopHandler import LoopHandler
from Music.VulkanBot import VulkanBot
class LoopAllButton(Button):
def __init__(self, bot: VulkanBot):
super().__init__(label="Loop All", style=ButtonStyle.secondary, emoji=VEmojis().LOOP_ALL)
self.__bot = bot
async def callback(self, interaction: Interaction) -> None:
await interaction.response.defer()
handler = LoopHandler(interaction, self.__bot)
response = await handler.run('all')
if response.embed:
await interaction.followup.send(embed=response.embed)

View File

@ -0,0 +1,20 @@
from discord import ButtonStyle, Interaction
from discord.ui import Button
from Config.Emojis import VEmojis
from Handlers.LoopHandler import LoopHandler
from Music.VulkanBot import VulkanBot
class LoopOffButton(Button):
def __init__(self, bot: VulkanBot):
super().__init__(label="Loop Off", style=ButtonStyle.secondary, emoji=VEmojis().LOOP_OFF)
self.__bot = bot
async def callback(self, interaction: Interaction) -> None:
await interaction.response.defer()
handler = LoopHandler(interaction, self.__bot)
response = await handler.run('off')
if response.embed:
await interaction.followup.send(embed=response.embed)

View File

@ -0,0 +1,20 @@
from discord import ButtonStyle, Interaction
from discord.ui import Button
from Config.Emojis import VEmojis
from Handlers.LoopHandler import LoopHandler
from Music.VulkanBot import VulkanBot
class LoopOneButton(Button):
def __init__(self, bot: VulkanBot):
super().__init__(label="Loop One", style=ButtonStyle.secondary, emoji=VEmojis().LOOP_ONE)
self.__bot = bot
async def callback(self, interaction: Interaction) -> None:
await interaction.response.defer()
handler = LoopHandler(interaction, self.__bot)
response = await handler.run('one')
if response.embed:
await interaction.followup.send(embed=response.embed)

20
UI/Buttons/PauseButton.py Normal file
View File

@ -0,0 +1,20 @@
from discord import ButtonStyle, Interaction
from discord.ui import Button
from Config.Emojis import VEmojis
from Handlers.PauseHandler import PauseHandler
from Music.VulkanBot import VulkanBot
class PauseButton(Button):
def __init__(self, bot: VulkanBot):
super().__init__(label="Pause", style=ButtonStyle.secondary, emoji=VEmojis().PAUSE)
self.__bot = bot
async def callback(self, interaction: Interaction) -> None:
await interaction.response.defer()
handler = PauseHandler(interaction, self.__bot)
response = await handler.run()
if response.embed:
await interaction.followup.send(embed=response.embed)

20
UI/Buttons/PlayButton.py Normal file
View File

@ -0,0 +1,20 @@
from discord import ButtonStyle, Interaction
from discord.ui import Button
from Config.Emojis import VEmojis
from Music.VulkanBot import VulkanBot
from Handlers.ResumeHandler import ResumeHandler
class PlayButton(Button):
def __init__(self, bot: VulkanBot):
super().__init__(label="Play", style=ButtonStyle.secondary, emoji=VEmojis().PLAY)
self.__bot = bot
async def callback(self, interaction: Interaction) -> None:
await interaction.response.defer()
handler = ResumeHandler(interaction, self.__bot)
response = await handler.run()
if response.embed:
await interaction.followup.send(embed=response.embed)

20
UI/Buttons/SkipButton.py Normal file
View File

@ -0,0 +1,20 @@
from discord import ButtonStyle, Interaction
from discord.ui import Button
from Config.Emojis import VEmojis
from Music.VulkanBot import VulkanBot
from Handlers.SkipHandler import SkipHandler
class SkipButton(Button):
def __init__(self, bot: VulkanBot):
super().__init__(label="Skip", style=ButtonStyle.secondary, emoji=VEmojis().SKIP)
self.__bot = bot
async def callback(self, interaction: Interaction) -> None:
await interaction.response.defer()
handler = SkipHandler(interaction, self.__bot)
response = await handler.run()
if response.embed:
await interaction.followup.send(embed=response.embed)

20
UI/Buttons/SongsButton.py Normal file
View File

@ -0,0 +1,20 @@
from Handlers.QueueHandler import QueueHandler
from discord import ButtonStyle, Interaction
from discord.ui import Button
from Config.Emojis import VEmojis
from Music.VulkanBot import VulkanBot
class SongsButton(Button):
def __init__(self, bot: VulkanBot):
super().__init__(label="Songs", style=ButtonStyle.secondary, emoji=VEmojis().QUEUE)
self.__bot = bot
async def callback(self, interaction: Interaction) -> None:
await interaction.response.defer()
handler = QueueHandler(interaction, self.__bot)
response = await handler.run()
if response.embed:
await interaction.followup.send(embed=response.embed)

20
UI/Buttons/StopButton.py Normal file
View File

@ -0,0 +1,20 @@
from discord import ButtonStyle, Interaction
from discord.ui import Button
from Config.Emojis import VEmojis
from Music.VulkanBot import VulkanBot
from Handlers.StopHandler import StopHandler
class StopButton(Button):
def __init__(self, bot: VulkanBot):
super().__init__(label="Stop", style=ButtonStyle.secondary, emoji=VEmojis().STOP)
self.__bot = bot
async def callback(self, interaction: Interaction) -> None:
await interaction.response.defer()
handler = StopHandler(interaction, self.__bot)
response = await handler.run()
if response.embed:
await interaction.followup.send(embed=response.embed)

View File

@ -1,22 +1,23 @@
from abc import ABC, abstractmethod
from Handlers.HandlerResponse import HandlerResponse
from discord.ext.commands import Context
from discord import Client, Message
from discord import Message
from Music.VulkanBot import VulkanBot
class AbstractView(ABC):
class AbstractCommandResponse(ABC):
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
self.__bot: VulkanBot = response.ctx.bot
@property
def response(self) -> HandlerResponse:
return self.__response
@property
def bot(self) -> Client:
def bot(self) -> VulkanBot:
return self.__bot
@property

View File

@ -1,8 +1,8 @@
from Views.AbstractView import AbstractView
from UI.Responses.AbstractCogResponse import AbstractCommandResponse
from Handlers.HandlerResponse import HandlerResponse
class EmbedView(AbstractView):
class EmbedCommandResponse(AbstractCommandResponse):
def __init__(self, response: HandlerResponse) -> None:
super().__init__(response)

View File

@ -0,0 +1,16 @@
from Config.Emojis import VEmojis
from UI.Responses.AbstractCogResponse import AbstractCommandResponse
from Handlers.HandlerResponse import HandlerResponse
class EmoteCommandResponse(AbstractCommandResponse):
def __init__(self, response: HandlerResponse) -> None:
super().__init__(response)
self.__emojis = VEmojis()
async def run(self) -> None:
if self.response.success:
await self.message.add_reaction(self.__emojis.SUCCESS)
else:
await self.message.add_reaction(self.__emojis.ERROR)

43
UI/Views/PlayerView.py Normal file
View File

@ -0,0 +1,43 @@
from discord import Message
from discord.ui import View
from Config.Emojis import VEmojis
from UI.Buttons.PauseButton import PauseButton
from UI.Buttons.BackButton import BackButton
from UI.Buttons.SkipButton import SkipButton
from UI.Buttons.StopButton import StopButton
from UI.Buttons.SongsButton import SongsButton
from UI.Buttons.PlayButton import PlayButton
from UI.Buttons.LoopAllButton import LoopAllButton
from UI.Buttons.LoopOneButton import LoopOneButton
from UI.Buttons.LoopOffButton import LoopOffButton
from Music.VulkanBot import VulkanBot
emojis = VEmojis()
class PlayerView(View):
def __init__(self, bot: VulkanBot, timeout: float = 6000):
super().__init__(timeout=timeout)
self.__bot = bot
self.__message: Message = None
self.add_item(BackButton(self.__bot))
self.add_item(PauseButton(self.__bot))
self.add_item(PlayButton(self.__bot))
self.add_item(StopButton(self.__bot))
self.add_item(SkipButton(self.__bot))
self.add_item(SongsButton(self.__bot))
self.add_item(LoopOneButton(self.__bot))
self.add_item(LoopOffButton(self.__bot))
self.add_item(LoopAllButton(self.__bot))
async def on_timeout(self) -> None:
# Disable all itens and, if has the message, edit it
try:
self.disable_all_items()
if self.__message is not None and isinstance(self.__message, Message):
await self.__message.edit(view=self)
except Exception as e:
print(f'[ERROR EDITING MESSAGE] -> {e}')
def set_message(self, message: Message) -> None:
self.__message = message

View File

@ -1,16 +1,17 @@
from typing import List
from discord.ext.commands import Context
from discord import Client, Message, Embed
from discord import Message, Embed
from Config.Singleton import Singleton
from Music.VulkanBot import VulkanBot
class Cleaner(Singleton):
def __init__(self, bot: Client = None) -> None:
def __init__(self, bot: VulkanBot = None) -> None:
if not super().created:
self.__bot = bot
self.__clean_str = 'Uploader:'
def set_bot(self, bot: Client) -> None:
def set_bot(self, bot: VulkanBot) -> None:
self.__bot = bot
async def clean_messages(self, ctx: Context, quant: int) -> None:

View File

@ -1,8 +1,8 @@
import re
import asyncio
from Config.Configs import Configs
from Config.Configs import VConfigs
from functools import wraps, partial
config = Configs()
config = VConfigs()
class Utils:

View File

@ -1,14 +0,0 @@
from Views.AbstractView import AbstractView
from Handlers.HandlerResponse import HandlerResponse
class EmoteView(AbstractView):
def __init__(self, response: HandlerResponse) -> None:
super().__init__(response)
async def run(self) -> None:
if self.response.success:
await self.message.add_reaction('')
else:
await self.message.add_reaction('')

39
main.py
View File

@ -1,38 +1,7 @@
from discord import Intents, Client
from os import listdir
from Config.Configs import Configs
from discord.ext.commands import Bot
class VulkanInitializer:
def __init__(self) -> None:
self.__config = Configs()
self.__intents = Intents.default()
self.__intents.members = True
self.__bot = self.__create_bot()
self.__add_cogs(self.__bot)
def __create_bot(self) -> Client:
bot = Bot(command_prefix=self.__config.BOT_PREFIX,
pm_help=True,
case_insensitive=True,
intents=self.__intents)
bot.remove_command('help')
return bot
def __add_cogs(self, bot: Client) -> None:
for filename in listdir(f'./{self.__config.COMMANDS_PATH}'):
if filename.endswith('.py'):
bot.load_extension(f'{self.__config.COMMANDS_PATH}.{filename[:-3]}')
def run(self) -> None:
if self.__config.BOT_TOKEN == '':
print('DEVELOPER NOTE -> Token not found')
exit()
self.__bot.run(self.__config.BOT_TOKEN, bot=True, reconnect=True)
from Music.VulkanInitializer import VulkanInitializer
if __name__ == '__main__':
vulkan = VulkanInitializer()
vulkan.run()
initializer = VulkanInitializer(willListen=True)
vulkanBot = initializer.getBot()
vulkanBot.startBot()

Binary file not shown.