mirror of
https://github.com/RafaelSolVargas/Vulkan.git
synced 2025-10-29 16:57:23 +00:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4e9e46d6d | ||
|
|
2ffbab86eb | ||
|
|
75de60470f | ||
|
|
afb223eadd | ||
|
|
8dfa3579ae | ||
|
|
5cdc4e9a53 | ||
|
|
7310eda1a1 | ||
|
|
a72c4c7d8d | ||
|
|
10e38a8809 | ||
|
|
3b198cf78a | ||
|
|
ef66bf8bcb | ||
|
|
d10264b97c | ||
|
|
ba57a3e18d | ||
|
|
5f60c12179 | ||
|
|
de5aed380b | ||
|
|
2794f1a6d0 | ||
|
|
2d27a2f080 | ||
|
|
15f8ea7cb2 | ||
|
|
0c20f68c2b | ||
|
|
2627f95a6d | ||
|
|
6ba7734a36 | ||
|
|
4fd23c56b6 | ||
|
|
5b61947904 | ||
|
|
a9cfaf62a4 | ||
|
|
a5cecd85d4 | ||
|
|
7f1ffb6b23 | ||
|
|
c5885f3093 | ||
|
|
60a36425ee | ||
|
|
5902a0dc72 | ||
|
|
ca754c6f62 | ||
|
|
4f11506c2b | ||
|
|
beb0bc085d | ||
|
|
fededdbb8c | ||
|
|
4a22b43ce9 | ||
|
|
48d7166386 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,4 @@
|
|||||||
.vscode
|
.vscode
|
||||||
assets/
|
|
||||||
__pycache__
|
__pycache__
|
||||||
.env
|
.env
|
||||||
.cache
|
.cache
|
||||||
|
|||||||
BIN
Assets/playermenu.jpg
Normal file
BIN
Assets/playermenu.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
BIN
Assets/queuemessage.jpg
Normal file
BIN
Assets/queuemessage.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 111 KiB |
BIN
Assets/vulkan-logo.png
Normal file
BIN
Assets/vulkan-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
BIN
Assets/vulkancommands.jpg
Normal file
BIN
Assets/vulkancommands.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
@@ -1,7 +1,7 @@
|
|||||||
from Config.Singleton import Singleton
|
from Config.Singleton import Singleton
|
||||||
|
|
||||||
|
|
||||||
class Colors(Singleton):
|
class VColors(Singleton):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.__red = 0xDC143C
|
self.__red = 0xDC143C
|
||||||
self.__green = 0x1F8B4C
|
self.__green = 0x1F8B4C
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
|
import os
|
||||||
from decouple import config
|
from decouple import config
|
||||||
from Config.Singleton import Singleton
|
from Config.Singleton import Singleton
|
||||||
|
from Config.Folder import Folder
|
||||||
|
|
||||||
|
|
||||||
class Configs(Singleton):
|
class VConfigs(Singleton):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
if not super().created:
|
if not super().created:
|
||||||
|
# You can change this boolean to False if you want to prevent the Bot from auto disconnecting
|
||||||
|
# Resolution for the issue: https://github.com/RafaelSolVargas/Vulkan/issues/33
|
||||||
|
self.SHOULD_AUTO_DISCONNECT_WHEN_ALONE = False
|
||||||
|
|
||||||
self.BOT_PREFIX = '!'
|
self.BOT_PREFIX = '!'
|
||||||
try:
|
try:
|
||||||
self.BOT_TOKEN = config('BOT_TOKEN')
|
self.BOT_TOKEN = config('BOT_TOKEN')
|
||||||
@@ -17,11 +23,18 @@ class Configs(Singleton):
|
|||||||
|
|
||||||
self.CLEANER_MESSAGES_QUANT = 5
|
self.CLEANER_MESSAGES_QUANT = 5
|
||||||
self.ACQUIRE_LOCK_TIMEOUT = 10
|
self.ACQUIRE_LOCK_TIMEOUT = 10
|
||||||
self.COMMANDS_PATH = 'DiscordCogs'
|
self.QUEUE_VIEW_TIMEOUT = 120
|
||||||
self.VC_TIMEOUT = 600
|
self.COMMANDS_FOLDER_NAME = 'DiscordCogs'
|
||||||
|
self.COMMANDS_PATH = f'{Folder().rootFolder}{self.COMMANDS_FOLDER_NAME}'
|
||||||
|
self.VC_TIMEOUT = 300
|
||||||
|
|
||||||
|
self.CHANCE_SHOW_PROJECT = 15
|
||||||
|
self.PROJECT_URL = 'https://github.com/RafaelSolVargas/Vulkan'
|
||||||
|
self.SUPPORTING_ICON = 'https://i.pinimg.com/originals/d6/05/b4/d605b4f8c5d1c6ae20dc353ef9f091bd.png'
|
||||||
|
|
||||||
self.MAX_PLAYLIST_LENGTH = 50
|
self.MAX_PLAYLIST_LENGTH = 50
|
||||||
self.MAX_PLAYLIST_FORCED_LENGTH = 5
|
self.MAX_PLAYLIST_FORCED_LENGTH = 5
|
||||||
|
self.MAX_SONGS_IN_PAGE = 10
|
||||||
self.MAX_PRELOAD_SONGS = 15
|
self.MAX_PRELOAD_SONGS = 15
|
||||||
self.MAX_SONGS_HISTORY = 15
|
self.MAX_SONGS_HISTORY = 15
|
||||||
|
|
||||||
@@ -30,3 +43,9 @@ class Configs(Singleton):
|
|||||||
|
|
||||||
self.MY_ERROR_BAD_COMMAND = 'This string serves to verify if some error was raised by myself on purpose'
|
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'
|
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
|
||||||
|
|||||||
@@ -1,16 +1,24 @@
|
|||||||
|
from random import random
|
||||||
from Config.Messages import Messages
|
from Config.Messages import Messages
|
||||||
from Config.Exceptions import VulkanError
|
from Config.Exceptions import VulkanError
|
||||||
from discord import Embed
|
from discord import Embed
|
||||||
from Config.Configs import Configs
|
from Config.Configs import VConfigs
|
||||||
from Config.Colors import Colors
|
from Config.Colors import VColors
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
|
||||||
class Embeds:
|
class VEmbeds:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.__config = Configs()
|
self.__config = VConfigs()
|
||||||
self.__messages = Messages()
|
self.__messages = Messages()
|
||||||
self.__colors = Colors()
|
self.__colors = VColors()
|
||||||
|
|
||||||
|
def __willShowProject(self) -> bool:
|
||||||
|
return (random() * 100 < self.__config.CHANCE_SHOW_PROJECT)
|
||||||
|
|
||||||
|
def __addFooterContent(self, embed: Embed) -> Embed:
|
||||||
|
footerText = f'\u200b Please support this project by leaving a star: {self.__config.PROJECT_URL}'
|
||||||
|
return embed.set_footer(text=footerText, icon_url=self.__config.SUPPORTING_ICON)
|
||||||
|
|
||||||
def ONE_SONG_LOOPING(self, info: dict) -> Embed:
|
def ONE_SONG_LOOPING(self, info: dict) -> Embed:
|
||||||
title = self.__messages.ONE_SONG_LOOPING
|
title = self.__messages.ONE_SONG_LOOPING
|
||||||
@@ -34,6 +42,14 @@ class Embeds:
|
|||||||
)
|
)
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
|
def INVALID_INDEX(self) -> Embed:
|
||||||
|
embed = Embed(
|
||||||
|
title=self.__messages.BAD_COMMAND_TITLE,
|
||||||
|
description=self.__messages.INVALID_INDEX,
|
||||||
|
colour=self.__colors.BLACK
|
||||||
|
)
|
||||||
|
return embed
|
||||||
|
|
||||||
def SONG_ADDED_TWO(self, info: dict, pos: int) -> Embed:
|
def SONG_ADDED_TWO(self, info: dict, pos: int) -> Embed:
|
||||||
embed = self.SONG_INFO(info, self.__messages.SONG_ADDED_TWO, pos)
|
embed = self.SONG_INFO(info, self.__messages.SONG_ADDED_TWO, pos)
|
||||||
return embed
|
return embed
|
||||||
@@ -105,6 +121,8 @@ class Embeds:
|
|||||||
value=position,
|
value=position,
|
||||||
inline=True)
|
inline=True)
|
||||||
|
|
||||||
|
if self.__willShowProject():
|
||||||
|
embedvc = self.__addFooterContent(embedvc)
|
||||||
return embedvc
|
return embedvc
|
||||||
|
|
||||||
def SONG_MOVED(self, song_name: str, pos1: int, pos2: int) -> Embed:
|
def SONG_MOVED(self, song_name: str, pos1: int, pos2: int) -> Embed:
|
||||||
@@ -162,6 +180,14 @@ class Embeds:
|
|||||||
)
|
)
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
|
def INVALID_ARGUMENTS(self):
|
||||||
|
embed = Embed(
|
||||||
|
title=self.__messages.BAD_COMMAND_TITLE,
|
||||||
|
description=self.__messages.INVALID_ARGUMENTS,
|
||||||
|
colour=self.__colors.BLACK
|
||||||
|
)
|
||||||
|
return embed
|
||||||
|
|
||||||
def COMMAND_NOT_FOUND(self) -> Embed:
|
def COMMAND_NOT_FOUND(self) -> Embed:
|
||||||
embed = Embed(
|
embed = Embed(
|
||||||
title=self.__messages.COMMAND_NOT_FOUND_TITLE,
|
title=self.__messages.COMMAND_NOT_FOUND_TITLE,
|
||||||
@@ -261,6 +287,41 @@ class Embeds:
|
|||||||
)
|
)
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
|
def PLAYER_RESUMED(self) -> Embed:
|
||||||
|
embed = Embed(
|
||||||
|
title=self.__messages.SONG_RESUMED,
|
||||||
|
colour=self.__colors.BLUE
|
||||||
|
)
|
||||||
|
return embed
|
||||||
|
|
||||||
|
def SKIPPING_SONG(self) -> Embed:
|
||||||
|
embed = Embed(
|
||||||
|
title=self.__messages.SONG_SKIPPED,
|
||||||
|
colour=self.__colors.BLUE
|
||||||
|
)
|
||||||
|
return embed
|
||||||
|
|
||||||
|
def STOPPING_PLAYER(self) -> Embed:
|
||||||
|
embed = Embed(
|
||||||
|
title=self.__messages.STOPPING,
|
||||||
|
colour=self.__colors.BLUE
|
||||||
|
)
|
||||||
|
return embed
|
||||||
|
|
||||||
|
def RETURNING_SONG(self) -> Embed:
|
||||||
|
embed = Embed(
|
||||||
|
title=self.__messages.RETURNING_SONG,
|
||||||
|
colour=self.__colors.BLUE
|
||||||
|
)
|
||||||
|
return embed
|
||||||
|
|
||||||
|
def PLAYER_PAUSED(self) -> Embed:
|
||||||
|
embed = Embed(
|
||||||
|
title=self.__messages.SONG_PAUSED,
|
||||||
|
colour=self.__colors.BLUE
|
||||||
|
)
|
||||||
|
return embed
|
||||||
|
|
||||||
def NOT_PREVIOUS_SONG(self) -> Embed:
|
def NOT_PREVIOUS_SONG(self) -> Embed:
|
||||||
embed = Embed(
|
embed = Embed(
|
||||||
title=self.__messages.SONG_PLAYER,
|
title=self.__messages.SONG_PLAYER,
|
||||||
@@ -289,6 +350,9 @@ class Embeds:
|
|||||||
description=description,
|
description=description,
|
||||||
colour=self.__colors.BLUE
|
colour=self.__colors.BLUE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.__willShowProject():
|
||||||
|
embed = self.__addFooterContent(embed)
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
def INVITE(self, bot_id: str) -> Embed:
|
def INVITE(self, bot_id: str) -> Embed:
|
||||||
@@ -332,9 +396,14 @@ class Embeds:
|
|||||||
)
|
)
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
|
def PLAYLIST_CLEAR(self) -> Embed:
|
||||||
|
return Embed(
|
||||||
|
description=self.__messages.PLAYLIST_CLEAR
|
||||||
|
)
|
||||||
|
|
||||||
def CARA_COROA(self, result: str) -> Embed:
|
def CARA_COROA(self, result: str) -> Embed:
|
||||||
embed = Embed(
|
embed = Embed(
|
||||||
title='Cara Cora',
|
title='Cara Coroa',
|
||||||
description=f'Result: {result}',
|
description=f'Result: {result}',
|
||||||
colour=self.__colors.GREEN
|
colour=self.__colors.GREEN
|
||||||
)
|
)
|
||||||
20
Config/Emojis.py
Normal file
20
Config/Emojis.py
Normal 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 = "✅"
|
||||||
@@ -79,6 +79,11 @@ class ErrorRemoving(VulkanError):
|
|||||||
super().__init__(message, title, *args)
|
super().__init__(message, title, *args)
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidIndex(VulkanError):
|
||||||
|
def __init__(self, message='', title='', *args: object) -> None:
|
||||||
|
super().__init__(message, title, *args)
|
||||||
|
|
||||||
|
|
||||||
class NumberRequired(VulkanError):
|
class NumberRequired(VulkanError):
|
||||||
def __init__(self, message='', title='', *args: object) -> None:
|
def __init__(self, message='', title='', *args: object) -> None:
|
||||||
super().__init__(message, title, *args)
|
super().__init__(message, title, *args)
|
||||||
|
|||||||
19
Config/Folder.py
Normal file
19
Config/Folder.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import os
|
||||||
|
from Config.Singleton import Singleton
|
||||||
|
|
||||||
|
|
||||||
|
class Folder(Singleton):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
if not self.created:
|
||||||
|
filePath = os.path.dirname(__file__)
|
||||||
|
self.rootFolder = self.__getRootFolder(filePath)
|
||||||
|
|
||||||
|
def __getRootFolder(self, current: str) -> str:
|
||||||
|
last_sep_index = -1
|
||||||
|
for x in range(len(current) - 1, -1, -1):
|
||||||
|
if current[x] == os.sep:
|
||||||
|
last_sep_index = x
|
||||||
|
break
|
||||||
|
|
||||||
|
path = current[:last_sep_index] + os.sep
|
||||||
|
return path
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
from Config.Singleton import Singleton
|
from Config.Singleton import Singleton
|
||||||
from Config.Configs import Configs
|
from Config.Configs import VConfigs
|
||||||
|
|
||||||
|
|
||||||
class Helper(Singleton):
|
class Helper(Singleton):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
if not super().created:
|
if not super().created:
|
||||||
config = Configs()
|
config = VConfigs()
|
||||||
self.HELP_SKIP = 'Skip the current playing song.'
|
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_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.'
|
self.HELP_RESUME = 'Resumes the song player.'
|
||||||
@@ -21,8 +21,8 @@ class Helper(Singleton):
|
|||||||
Off - Disable loop."""
|
Off - Disable loop."""
|
||||||
self.HELP_NP = 'Show the info of the current song.'
|
self.HELP_NP = 'Show the info of the current song.'
|
||||||
self.HELP_NP_LONG = 'Show the information of the song being played.\n\nRequire: A song being played.\nArguments: None.'
|
self.HELP_NP_LONG = 'Show the information of the song being played.\n\nRequire: A song being played.\nArguments: None.'
|
||||||
self.HELP_QUEUE = f'Show the first {config.MAX_PRELOAD_SONGS} songs in queue.'
|
self.HELP_QUEUE = f'Show the first {config.MAX_SONGS_IN_PAGE} songs in queue.'
|
||||||
self.HELP_QUEUE_LONG = f'Show the first {config.MAX_PRELOAD_SONGS} song in the queue.\n\nArguments: None.'
|
self.HELP_QUEUE_LONG = f'Show the first {config.MAX_SONGS_IN_PAGE} song in the queue.\n\nArguments: None.'
|
||||||
self.HELP_PAUSE = 'Pauses the song player.'
|
self.HELP_PAUSE = 'Pauses the song player.'
|
||||||
self.HELP_PAUSE_LONG = 'If playing, pauses the song player.\n\nArguments: None'
|
self.HELP_PAUSE_LONG = 'If playing, pauses the song player.\n\nArguments: None'
|
||||||
self.HELP_PREV = 'Play the previous song.'
|
self.HELP_PREV = 'Play the previous song.'
|
||||||
@@ -33,7 +33,7 @@ class Helper(Singleton):
|
|||||||
self.HELP_PLAY_LONG = 'Play a song in discord. \n\nRequire: You to be connected to a voice channel.\nArguments: Youtube, Spotify or Deezer song/playlist link or the title of the song to be searched in Youtube.'
|
self.HELP_PLAY_LONG = 'Play a song in discord. \n\nRequire: You to be connected to a voice channel.\nArguments: Youtube, Spotify or Deezer song/playlist link or the title of the song to be searched in Youtube.'
|
||||||
self.HELP_HISTORY = f'Show the history of played songs.'
|
self.HELP_HISTORY = f'Show the history of played songs.'
|
||||||
self.HELP_HISTORY_LONG = f'Show the last {config.MAX_SONGS_HISTORY} played songs'
|
self.HELP_HISTORY_LONG = f'Show the last {config.MAX_SONGS_HISTORY} played songs'
|
||||||
self.HELP_MOVE = 'Moves a song from position x to y in queue.'
|
self.HELP_MOVE = 'Moves a song from position pos1 to pos2 in queue.'
|
||||||
self.HELP_MOVE_LONG = 'Moves a song from position x to position y in queue.\n\nRequire: Positions to be both valid numbers.\nArguments: 1º Number => Initial position, 2º Number => Destination position. Both numbers could be -1 to refer to the last song in queue.\nDefault: By default, if the second number is not passed, it will be 1, moving the selected song to 1º position.'
|
self.HELP_MOVE_LONG = 'Moves a song from position x to position y in queue.\n\nRequire: Positions to be both valid numbers.\nArguments: 1º Number => Initial position, 2º Number => Destination position. Both numbers could be -1 to refer to the last song in queue.\nDefault: By default, if the second number is not passed, it will be 1, moving the selected song to 1º position.'
|
||||||
self.HELP_REMOVE = 'Remove a song in position x.'
|
self.HELP_REMOVE = 'Remove a song in position x.'
|
||||||
self.HELP_REMOVE_LONG = 'Remove a song from queue in the position passed.\n\nRequire: Position to be a valid number.\nArguments: 1º self.Number => Position in queue of the song.'
|
self.HELP_REMOVE_LONG = 'Remove a song from queue in the position passed.\n\nRequire: Position to be a valid number.\nArguments: 1º self.Number => Position in queue of the song.'
|
||||||
@@ -49,3 +49,6 @@ class Helper(Singleton):
|
|||||||
self.HELP_CHOOSE_LONG = 'Choose randomly one item passed in this command.\n\nRequire: Itens to be separated by comma.\nArguments: As much as you want.'
|
self.HELP_CHOOSE_LONG = 'Choose randomly one item passed in this command.\n\nRequire: Itens to be separated by comma.\nArguments: As much as you want.'
|
||||||
self.HELP_CARA = 'Return cara or coroa.'
|
self.HELP_CARA = 'Return cara or coroa.'
|
||||||
self.HELP_CARA_LONG = 'Return cara or coroa.'
|
self.HELP_CARA_LONG = 'Return cara or coroa.'
|
||||||
|
|
||||||
|
self.SLASH_QUEUE_DESCRIPTION = f'Number of queue page, there is only {config.MAX_SONGS_IN_PAGE} musics by page'
|
||||||
|
self.SLASH_MOVE_HELP = 'Moves a song from position pos1 to pos2 in queue.'
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
from Config.Singleton import Singleton
|
from Config.Singleton import Singleton
|
||||||
from Config.Configs import Configs
|
from Config.Configs import VConfigs
|
||||||
|
from Config.Emojis import VEmojis
|
||||||
|
|
||||||
|
|
||||||
class Messages(Singleton):
|
class Messages(Singleton):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
if not super().created:
|
if not super().created:
|
||||||
configs = Configs()
|
self.__emojis = VEmojis()
|
||||||
|
configs = VConfigs()
|
||||||
self.STARTUP_MESSAGE = 'Starting Vulkan...'
|
self.STARTUP_MESSAGE = 'Starting Vulkan...'
|
||||||
self.STARTUP_COMPLETE_MESSAGE = 'Vulkan is now operating.'
|
self.STARTUP_COMPLETE_MESSAGE = 'Vulkan is now operating.'
|
||||||
|
|
||||||
@@ -16,67 +18,73 @@ class Messages(Singleton):
|
|||||||
|
|
||||||
self.SONGS_ADDED = 'Downloading `{}` songs to add 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 = 'Downloading the song `{}` to add to the queue'
|
||||||
self.SONG_ADDED_TWO = '🎧 Song added to the queue'
|
self.SONG_ADDED_TWO = f'{self.__emojis.MUSIC} Song added to the queue'
|
||||||
self.SONG_PLAYING = '🎧 Song playing now'
|
self.SONG_PLAYING = f'{self.__emojis.MUSIC} Song playing now'
|
||||||
self.SONG_PLAYER = '🎧 Song Player'
|
self.SONG_PLAYER = f'{self.__emojis.MUSIC} Song Player'
|
||||||
self.QUEUE_TITLE = '🎧 Songs in Queue'
|
self.QUEUE_TITLE = f'{self.__emojis.MUSIC} Songs in Queue'
|
||||||
self.ONE_SONG_LOOPING = '🎧 Looping One Song'
|
self.ONE_SONG_LOOPING = f'{self.__emojis.MUSIC} Looping One Song'
|
||||||
self.ALL_SONGS_LOOPING = '🎧 Looping All Songs'
|
self.ALL_SONGS_LOOPING = f'{self.__emojis.MUSIC} Looping All Songs'
|
||||||
self.SONG_PAUSED = '⏸️ Song paused'
|
self.SONG_PAUSED = f'{self.__emojis.PAUSE} Song paused'
|
||||||
self.SONG_RESUMED = '▶️ Song playing'
|
self.SONG_RESUMED = f'{self.__emojis.PLAY} Song playing'
|
||||||
self.EMPTY_QUEUE = f'📜 Song queue is empty, use {configs.BOT_PREFIX}play to add new songs'
|
self.SONG_SKIPPED = f'{self.__emojis.SKIP} Song skipped'
|
||||||
self.SONG_DOWNLOADING = '📥 Downloading...'
|
self.RETURNING_SONG = f'{self.__emojis.BACK} Playing previous song'
|
||||||
|
self.STOPPING = f'{self.__emojis.STOP} Player Stopped'
|
||||||
|
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.PLAYLIST_CLEAR = f'{self.__emojis.MUSIC} Playlist is now empty'
|
||||||
|
|
||||||
self.HISTORY_TITLE = '🎧 Played Songs'
|
self.HISTORY_TITLE = f'{self.__emojis.MUSIC} Played Songs'
|
||||||
self.HISTORY_EMPTY = '📜 There is no musics in history'
|
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_MOVED_SUCCESSFULLY = 'Song `{}` in position `{}` moved to the position `{}` successfully'
|
||||||
self.SONG_REMOVED_SUCCESSFULLY = 'Song `{}` removed 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_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'❌ Vulkan is looping one song, 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 = '🔁 Vulkan is already looping all songs'
|
self.LOOP_ALL_ALREADY_ON = f'{self.__emojis.LOOP_ALL} Vulkan is already looping all songs'
|
||||||
self.LOOP_ONE_ALREADY_ON = '🔂 Vulkan is already looping the current song'
|
self.LOOP_ONE_ALREADY_ON = f'{self.__emojis.LOOP_ONE} Vulkan is already looping the current song'
|
||||||
self.LOOP_ALL_ACTIVATE = '🔁 Looping all songs'
|
self.LOOP_ALL_ACTIVATE = f'{self.__emojis.LOOP_ALL} Looping all songs'
|
||||||
self.LOOP_ONE_ACTIVATE = '🔂 Looping the current song'
|
self.LOOP_ONE_ACTIVATE = f'{self.__emojis.LOOP_ONE} Looping the current song'
|
||||||
self.LOOP_DISABLE = '➡️ Loop disabled'
|
self.LOOP_DISABLE = f'{self.__emojis.LOOP_OFF} Loop disabled'
|
||||||
self.LOOP_ALREADY_DISABLE = '❌ Loop is already disabled'
|
self.LOOP_ALREADY_DISABLE = f'{self.__emojis.ERROR} Loop is already disabled'
|
||||||
self.LOOP_ON = f'❌ This command cannot be invoked with any loop activated. Use {configs.BOT_PREFIX}loop off to disable loop'
|
self.LOOP_ON = f'{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"""❌ Invalid arguments of Loop command. Use {configs.BOT_PREFIX}help loop to more information.
|
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", ""]"""
|
-> Available Arguments: ["all", "off", "one", ""]"""
|
||||||
|
|
||||||
self.SONGS_SHUFFLED = '🔀 Songs shuffled successfully'
|
self.SONGS_SHUFFLED = f'{self.__emojis.SHUFFLE} Songs shuffled successfully'
|
||||||
self.ERROR_SHUFFLING = '❌ Error while shuffling the songs'
|
self.ERROR_SHUFFLING = f'{self.__emojis.ERROR} Error while shuffling the songs'
|
||||||
self.ERROR_MOVING = '❌ Error while moving the songs'
|
self.ERROR_MOVING = f'{self.__emojis.ERROR} Error while moving the songs'
|
||||||
self.LENGTH_ERROR = '❌ Numbers must be between 1 and queue length, use -1 for the last song'
|
self.LENGTH_ERROR = f'{self.__emojis.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_NUMBER = f'{self.__emojis.ERROR} This command require a number'
|
||||||
self.ERROR_PLAYING = '❌ Error while playing songs'
|
self.ERROR_PLAYING = f'{self.__emojis.ERROR} Error while playing songs'
|
||||||
self.COMMAND_NOT_FOUND = f'❌ Command not found, type {configs.BOT_PREFIX}help to see all commands'
|
self.COMMAND_NOT_FOUND = f'{self.__emojis.ERROR} 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.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'❌ Missing arguments in this command. Type {configs.BOT_PREFIX}help "command" to see more info about this command'
|
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 = '❌ There is none previous song to play'
|
self.NOT_PREVIOUS = f'{self.__emojis.ERROR} 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.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.IMPOSSIBLE_MOVE = 'That is impossible :('
|
||||||
self.ERROR_TITLE = 'Error :-('
|
self.ERROR_TITLE = 'Error :-('
|
||||||
self.COMMAND_NOT_FOUND_TITLE = 'This is strange :-('
|
self.COMMAND_NOT_FOUND_TITLE = 'This is strange :-('
|
||||||
self.NO_CHANNEL = 'To play some music, connect to any voice channel first.'
|
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.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.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.INVALID_INDEX = f'Invalid index passed as argument.'
|
||||||
self.EXTRACTING_ERROR = '❌ An error ocurred while searching for the songs'
|
self.INVALID_ARGUMENTS = f'Invalid arguments passed to command.'
|
||||||
|
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.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_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.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 = '❌ Sorry. This video is unavailable for download.'
|
self.VIDEO_UNAVAILABLE = f'{self.__emojis.ERROR} 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.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):
|
class SearchMessages(Singleton):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
if not super().created:
|
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 = 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.UNKNOWN_INPUT_TITLE = 'Nothing Found'
|
||||||
self.GENERIC_TITLE = 'URL could not be processed'
|
self.GENERIC_TITLE = 'URL could not be processed'
|
||||||
|
|||||||
@@ -1,24 +1,22 @@
|
|||||||
from discord import Client, Game, Status, Embed
|
from discord import Embed
|
||||||
from discord.ext.commands.errors import CommandNotFound, MissingRequiredArgument
|
from discord.ext.commands import Cog, command
|
||||||
from discord.ext import commands
|
from Config.Configs import VConfigs
|
||||||
from Config.Configs import Configs
|
|
||||||
from Config.Helper import Helper
|
from Config.Helper import Helper
|
||||||
from Config.Messages import Messages
|
from Config.Colors import VColors
|
||||||
from Config.Colors import Colors
|
from Music.VulkanBot import VulkanBot
|
||||||
from Views.Embeds import Embeds
|
from Config.Embeds import VEmbeds
|
||||||
|
|
||||||
helper = Helper()
|
helper = Helper()
|
||||||
|
|
||||||
|
|
||||||
class ControlCog(commands.Cog):
|
class ControlCog(Cog):
|
||||||
"""Class to handle discord events"""
|
"""Class to handle discord events"""
|
||||||
|
|
||||||
def __init__(self, bot: Client):
|
def __init__(self, bot: VulkanBot):
|
||||||
self.__bot = bot
|
self.__bot = bot
|
||||||
self.__config = Configs()
|
self.__config = VConfigs()
|
||||||
self.__messages = Messages()
|
self.__colors = VColors()
|
||||||
self.__colors = Colors()
|
self.__embeds = VEmbeds()
|
||||||
self.__embeds = Embeds()
|
|
||||||
self.__commands = {
|
self.__commands = {
|
||||||
'MUSIC': ['resume', 'pause', 'loop', 'stop',
|
'MUSIC': ['resume', 'pause', 'loop', 'stop',
|
||||||
'skip', 'play', 'queue', 'clear',
|
'skip', 'play', 'queue', 'clear',
|
||||||
@@ -28,28 +26,7 @@ class ControlCog(commands.Cog):
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@commands.Cog.listener()
|
@command(name="help", help=helper.HELP_HELP, description=helper.HELP_HELP_LONG, aliases=['h', 'ajuda'])
|
||||||
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'])
|
|
||||||
async def help_msg(self, ctx, command_help=''):
|
async def help_msg(self, ctx, command_help=''):
|
||||||
if command_help != '':
|
if command_help != '':
|
||||||
for command in self.__bot.commands:
|
for command in self.__bot.commands:
|
||||||
@@ -97,10 +74,10 @@ class ControlCog(commands.Cog):
|
|||||||
colour=self.__colors.BLUE
|
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)
|
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):
|
async def invite_bot(self, ctx):
|
||||||
invite_url = self.__config.INVITE_URL.format(self.__bot.user.id)
|
invite_url = self.__config.INVITE_URL.format(self.__bot.user.id)
|
||||||
txt = self.__config.INVITE_MESSAGE.format(invite_url, invite_url)
|
txt = self.__config.INVITE_MESSAGE.format(invite_url, invite_url)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
from discord import Guild, Client
|
from discord.ext.commands import Context, command, Cog
|
||||||
from discord.ext import commands
|
from Config.Exceptions import InvalidInput
|
||||||
from discord.ext.commands import Context
|
|
||||||
from Config.Helper import Helper
|
from Config.Helper import Helper
|
||||||
from Handlers.ClearHandler import ClearHandler
|
from Handlers.ClearHandler import ClearHandler
|
||||||
|
from Handlers.HandlerResponse import HandlerResponse
|
||||||
from Handlers.MoveHandler import MoveHandler
|
from Handlers.MoveHandler import MoveHandler
|
||||||
from Handlers.NowPlayingHandler import NowPlayingHandler
|
from Handlers.NowPlayingHandler import NowPlayingHandler
|
||||||
from Handlers.PlayHandler import PlayHandler
|
from Handlers.PlayHandler import PlayHandler
|
||||||
@@ -17,216 +17,242 @@ from Handlers.ResumeHandler import ResumeHandler
|
|||||||
from Handlers.HistoryHandler import HistoryHandler
|
from Handlers.HistoryHandler import HistoryHandler
|
||||||
from Handlers.QueueHandler import QueueHandler
|
from Handlers.QueueHandler import QueueHandler
|
||||||
from Handlers.LoopHandler import LoopHandler
|
from Handlers.LoopHandler import LoopHandler
|
||||||
from Views.EmoteView import EmoteView
|
from Messages.MessagesCategory import MessagesCategory
|
||||||
from Views.EmbedView import EmbedView
|
from Messages.Responses.EmoteCogResponse import EmoteCommandResponse
|
||||||
|
from Messages.Responses.EmbedCogResponse import EmbedCommandResponse
|
||||||
|
from Music.VulkanBot import VulkanBot
|
||||||
|
from Config.Configs import VConfigs
|
||||||
|
from Config.Embeds import VEmbeds
|
||||||
|
from Parallelism.ProcessManager import ProcessManager
|
||||||
|
|
||||||
helper = Helper()
|
helper = Helper()
|
||||||
|
|
||||||
|
|
||||||
class MusicCog(commands.Cog):
|
class MusicCog(Cog):
|
||||||
"""
|
"""
|
||||||
Class to listen to Music commands
|
Class to listen to Music commands
|
||||||
It'll listen for commands from discord, when triggered will create a specific Handler for the command
|
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
|
Execute the handler and then create a specific View to be showed in Discord
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, bot) -> None:
|
def __init__(self, bot: VulkanBot) -> None:
|
||||||
self.__bot: Client = bot
|
self.__bot: VulkanBot = bot
|
||||||
|
self.__embeds = VEmbeds()
|
||||||
|
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:
|
async def play(self, ctx: Context, *args) -> None:
|
||||||
try:
|
try:
|
||||||
controller = PlayHandler(ctx, self.__bot)
|
controller = PlayHandler(ctx, self.__bot)
|
||||||
|
|
||||||
response = await controller.run(args)
|
if len(args) > 1:
|
||||||
|
track = " ".join(args)
|
||||||
|
else:
|
||||||
|
track = args[0]
|
||||||
|
|
||||||
|
response = await controller.run(track)
|
||||||
if response is not None:
|
if response is not None:
|
||||||
view1 = EmbedView(response)
|
cogResponser1 = EmbedCommandResponse(response, MessagesCategory.PLAYER)
|
||||||
view2 = EmoteView(response)
|
cogResponser2 = EmoteCommandResponse(response, MessagesCategory.PLAYER)
|
||||||
await view1.run()
|
await cogResponser1.run()
|
||||||
await view2.run()
|
await cogResponser2.run()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'[ERROR IN COG] -> {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:
|
async def queue(self, ctx: Context, *args) -> None:
|
||||||
try:
|
try:
|
||||||
|
pageNumber = " ".join(args)
|
||||||
|
|
||||||
controller = QueueHandler(ctx, self.__bot)
|
controller = QueueHandler(ctx, self.__bot)
|
||||||
|
|
||||||
response = await controller.run()
|
if pageNumber == "":
|
||||||
view2 = EmbedView(response)
|
response = await controller.run()
|
||||||
await view2.run()
|
else:
|
||||||
|
pageNumber = int(pageNumber)
|
||||||
|
pageNumber -= 1 # Change index 1 to 0
|
||||||
|
response = await controller.run(pageNumber)
|
||||||
|
|
||||||
|
cogResponser = EmbedCommandResponse(response, MessagesCategory.QUEUE)
|
||||||
|
await cogResponser.run()
|
||||||
|
except ValueError as e:
|
||||||
|
# Draft a Handler Response to pass to cogResponser
|
||||||
|
error = InvalidInput()
|
||||||
|
embed = self.__embeds.INVALID_ARGUMENTS()
|
||||||
|
response = HandlerResponse(ctx, embed, error)
|
||||||
|
|
||||||
|
cogResponser = EmbedCommandResponse(response, MessagesCategory.QUEUE)
|
||||||
|
await cogResponser.run(deleteLast=False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'[ERROR IN COG] -> {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:
|
async def skip(self, ctx: Context) -> None:
|
||||||
try:
|
try:
|
||||||
controller = SkipHandler(ctx, self.__bot)
|
controller = SkipHandler(ctx, self.__bot)
|
||||||
|
|
||||||
response = await controller.run()
|
response = await controller.run()
|
||||||
if response.success:
|
cogResponser1 = EmbedCommandResponse(response, MessagesCategory.PLAYER)
|
||||||
view = EmoteView(response)
|
cogResponser2 = EmoteCommandResponse(response, MessagesCategory.PLAYER)
|
||||||
else:
|
await cogResponser1.run()
|
||||||
view = EmbedView(response)
|
await cogResponser2.run()
|
||||||
|
|
||||||
await view.run()
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'[ERROR IN COG] -> {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:
|
async def stop(self, ctx: Context) -> None:
|
||||||
try:
|
try:
|
||||||
controller = StopHandler(ctx, self.__bot)
|
controller = StopHandler(ctx, self.__bot)
|
||||||
|
|
||||||
response = await controller.run()
|
response = await controller.run()
|
||||||
if response.success:
|
cogResponser1 = EmbedCommandResponse(response, MessagesCategory.PLAYER)
|
||||||
view = EmoteView(response)
|
cogResponser2 = EmoteCommandResponse(response, MessagesCategory.PLAYER)
|
||||||
else:
|
await cogResponser1.run()
|
||||||
view = EmbedView(response)
|
await cogResponser2.run()
|
||||||
|
|
||||||
await view.run()
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'[ERROR IN COG] -> {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:
|
async def pause(self, ctx: Context) -> None:
|
||||||
try:
|
try:
|
||||||
controller = PauseHandler(ctx, self.__bot)
|
controller = PauseHandler(ctx, self.__bot)
|
||||||
|
|
||||||
response = await controller.run()
|
response = await controller.run()
|
||||||
view1 = EmoteView(response)
|
cogResponser1 = EmoteCommandResponse(response, MessagesCategory.PLAYER)
|
||||||
view2 = EmbedView(response)
|
cogResponser2 = EmbedCommandResponse(response, MessagesCategory.PLAYER)
|
||||||
await view1.run()
|
await cogResponser1.run()
|
||||||
await view2.run()
|
await cogResponser2.run()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'[ERROR IN COG] -> {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:
|
async def resume(self, ctx: Context) -> None:
|
||||||
try:
|
try:
|
||||||
controller = ResumeHandler(ctx, self.__bot)
|
controller = ResumeHandler(ctx, self.__bot)
|
||||||
|
|
||||||
response = await controller.run()
|
response = await controller.run()
|
||||||
view1 = EmoteView(response)
|
cogResponser1 = EmoteCommandResponse(response, MessagesCategory.PLAYER)
|
||||||
view2 = EmbedView(response)
|
cogResponser2 = EmbedCommandResponse(response, MessagesCategory.PLAYER)
|
||||||
await view1.run()
|
await cogResponser1.run()
|
||||||
await view2.run()
|
await cogResponser2.run()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'[ERROR IN COG] -> {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:
|
async def prev(self, ctx: Context) -> None:
|
||||||
try:
|
try:
|
||||||
controller = PrevHandler(ctx, self.__bot)
|
controller = PrevHandler(ctx, self.__bot)
|
||||||
|
|
||||||
response = await controller.run()
|
response = await controller.run()
|
||||||
if response is not None:
|
if response is not None:
|
||||||
view1 = EmbedView(response)
|
cogResponser1 = EmbedCommandResponse(response, MessagesCategory.PLAYER)
|
||||||
view2 = EmoteView(response)
|
cogResponser2 = EmoteCommandResponse(response, MessagesCategory.PLAYER)
|
||||||
await view1.run()
|
await cogResponser1.run()
|
||||||
await view2.run()
|
await cogResponser2.run()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'[ERROR IN COG] -> {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:
|
async def history(self, ctx: Context) -> None:
|
||||||
try:
|
try:
|
||||||
controller = HistoryHandler(ctx, self.__bot)
|
controller = HistoryHandler(ctx, self.__bot)
|
||||||
|
|
||||||
response = await controller.run()
|
response = await controller.run()
|
||||||
view1 = EmbedView(response)
|
cogResponser1 = EmbedCommandResponse(response, MessagesCategory.HISTORY)
|
||||||
view2 = EmoteView(response)
|
cogResponser2 = EmoteCommandResponse(response, MessagesCategory.HISTORY)
|
||||||
await view1.run()
|
await cogResponser1.run()
|
||||||
await view2.run()
|
await cogResponser2.run()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'[ERROR IN COG] -> {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:
|
async def loop(self, ctx: Context, args='') -> None:
|
||||||
try:
|
try:
|
||||||
controller = LoopHandler(ctx, self.__bot)
|
controller = LoopHandler(ctx, self.__bot)
|
||||||
|
|
||||||
response = await controller.run(args)
|
response = await controller.run(args)
|
||||||
view1 = EmoteView(response)
|
cogResponser1 = EmoteCommandResponse(response, MessagesCategory.LOOP)
|
||||||
view2 = EmbedView(response)
|
cogResponser2 = EmbedCommandResponse(response, MessagesCategory.LOOP)
|
||||||
await view1.run()
|
await cogResponser1.run()
|
||||||
await view2.run()
|
await cogResponser2.run()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'[ERROR IN COG] -> {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:
|
async def clear(self, ctx: Context) -> None:
|
||||||
try:
|
try:
|
||||||
controller = ClearHandler(ctx, self.__bot)
|
controller = ClearHandler(ctx, self.__bot)
|
||||||
|
|
||||||
response = await controller.run()
|
response = await controller.run()
|
||||||
view = EmoteView(response)
|
cogResponser1 = EmbedCommandResponse(response, MessagesCategory.PLAYER)
|
||||||
await view.run()
|
cogResponser2 = EmoteCommandResponse(response, MessagesCategory.PLAYER)
|
||||||
|
await cogResponser1.run()
|
||||||
|
await cogResponser2.run()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'[ERROR IN COG] -> {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:
|
async def now_playing(self, ctx: Context) -> None:
|
||||||
try:
|
try:
|
||||||
controller = NowPlayingHandler(ctx, self.__bot)
|
controller = NowPlayingHandler(ctx, self.__bot)
|
||||||
|
|
||||||
response = await controller.run()
|
response = await controller.run()
|
||||||
view1 = EmbedView(response)
|
cogResponser1 = EmbedCommandResponse(response, MessagesCategory.NOW_PLAYING)
|
||||||
view2 = EmoteView(response)
|
cogResponser2 = EmoteCommandResponse(response, MessagesCategory.NOW_PLAYING)
|
||||||
await view1.run()
|
await cogResponser1.run()
|
||||||
await view2.run()
|
await cogResponser2.run()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'[ERROR IN COG] -> {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:
|
async def shuffle(self, ctx: Context) -> None:
|
||||||
try:
|
try:
|
||||||
controller = ShuffleHandler(ctx, self.__bot)
|
controller = ShuffleHandler(ctx, self.__bot)
|
||||||
|
|
||||||
response = await controller.run()
|
response = await controller.run()
|
||||||
view1 = EmbedView(response)
|
cogResponser1 = EmbedCommandResponse(response, MessagesCategory.PLAYER)
|
||||||
view2 = EmoteView(response)
|
cogResponser2 = EmoteCommandResponse(response, MessagesCategory.PLAYER)
|
||||||
await view1.run()
|
await cogResponser1.run()
|
||||||
await view2.run()
|
await cogResponser2.run()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'[ERROR IN COG] -> {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:
|
async def move(self, ctx: Context, pos1, pos2='1') -> None:
|
||||||
try:
|
try:
|
||||||
controller = MoveHandler(ctx, self.__bot)
|
controller = MoveHandler(ctx, self.__bot)
|
||||||
|
|
||||||
response = await controller.run(pos1, pos2)
|
response = await controller.run(pos1, pos2)
|
||||||
view1 = EmbedView(response)
|
cogResponser1 = EmbedCommandResponse(response, MessagesCategory.MANAGING_QUEUE)
|
||||||
view2 = EmoteView(response)
|
cogResponser2 = EmoteCommandResponse(response, MessagesCategory.MANAGING_QUEUE)
|
||||||
await view1.run()
|
await cogResponser1.run()
|
||||||
await view2.run()
|
await cogResponser2.run()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'[ERROR IN COG] -> {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:
|
async def remove(self, ctx: Context, position) -> None:
|
||||||
try:
|
try:
|
||||||
controller = RemoveHandler(ctx, self.__bot)
|
controller = RemoveHandler(ctx, self.__bot)
|
||||||
|
|
||||||
response = await controller.run(position)
|
response = await controller.run(position)
|
||||||
view1 = EmbedView(response)
|
cogResponser1 = EmbedCommandResponse(response, MessagesCategory.MANAGING_QUEUE)
|
||||||
view2 = EmoteView(response)
|
cogResponser2 = EmoteCommandResponse(response, MessagesCategory.MANAGING_QUEUE)
|
||||||
await view1.run()
|
await cogResponser1.run()
|
||||||
await view2.run()
|
await cogResponser2.run()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'[ERROR IN COG] -> {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:
|
async def reset(self, ctx: Context) -> None:
|
||||||
try:
|
try:
|
||||||
controller = ResetHandler(ctx, self.__bot)
|
controller = ResetHandler(ctx, self.__bot)
|
||||||
|
|
||||||
response = await controller.run()
|
response = await controller.run()
|
||||||
view1 = EmbedView(response)
|
cogResponser1 = EmbedCommandResponse(response, MessagesCategory.PLAYER)
|
||||||
view2 = EmoteView(response)
|
cogResponser2 = EmoteCommandResponse(response, MessagesCategory.PLAYER)
|
||||||
await view1.run()
|
await cogResponser1.run()
|
||||||
await view2.run()
|
await cogResponser2.run()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'[ERROR IN COG] -> {e}')
|
print(f'[ERROR IN COG] -> {e}')
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
from random import randint, random
|
from random import randint, random
|
||||||
from discord import Client
|
from Music.VulkanBot import VulkanBot
|
||||||
from discord.ext.commands import Context, command, Cog
|
from discord.ext.commands import Context, command, Cog
|
||||||
from Config.Helper import Helper
|
from Config.Helper import Helper
|
||||||
from Views.Embeds import Embeds
|
from Config.Embeds import VEmbeds
|
||||||
|
|
||||||
helper = Helper()
|
helper = Helper()
|
||||||
|
|
||||||
@@ -10,8 +10,8 @@ helper = Helper()
|
|||||||
class RandomCog(Cog):
|
class RandomCog(Cog):
|
||||||
"""Class to listen to commands of type Random"""
|
"""Class to listen to commands of type Random"""
|
||||||
|
|
||||||
def __init__(self, bot: Client):
|
def __init__(self, bot: VulkanBot):
|
||||||
self.__embeds = Embeds()
|
self.__embeds = VEmbeds()
|
||||||
|
|
||||||
@command(name='random', help=helper.HELP_RANDOM, description=helper.HELP_RANDOM_LONG, aliases=['rand'])
|
@command(name='random', help=helper.HELP_RANDOM, description=helper.HELP_RANDOM_LONG, aliases=['rand'])
|
||||||
async def random(self, ctx: Context, arg: str) -> None:
|
async def random(self, ctx: Context, arg: str) -> None:
|
||||||
|
|||||||
271
DiscordCogs/SlashCog.py
Normal file
271
DiscordCogs/SlashCog.py
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
from discord.ext.commands import slash_command, Cog
|
||||||
|
from discord import Option, ApplicationContext, OptionChoice
|
||||||
|
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 Messages.MessagesCategory import MessagesCategory
|
||||||
|
from Messages.Responses.SlashEmbedResponse import SlashEmbedResponse
|
||||||
|
from Music.VulkanBot import VulkanBot
|
||||||
|
from Config.Embeds import VEmbeds
|
||||||
|
from Config.Helper import Helper
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
helper = Helper()
|
||||||
|
|
||||||
|
|
||||||
|
class SlashCommands(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: VulkanBot) -> None:
|
||||||
|
self.__bot: VulkanBot = bot
|
||||||
|
self.__embeds = VEmbeds()
|
||||||
|
|
||||||
|
@slash_command(name="play", description=helper.HELP_PLAY)
|
||||||
|
async def play(self, ctx: ApplicationContext,
|
||||||
|
music: Option(str, "The music name or URL", required=True)) -> None:
|
||||||
|
# Due to the utilization of multiprocessing module in this Project, we have multiple instances of the Bot, and by using this flag
|
||||||
|
# we can control witch bot instance will listen to the commands that Discord send to our application
|
||||||
|
if not self.__bot.listingSlash:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
await ctx.defer()
|
||||||
|
controller = PlayHandler(ctx, self.__bot)
|
||||||
|
|
||||||
|
response = await controller.run(music)
|
||||||
|
if response is not None:
|
||||||
|
cogResponser1 = SlashEmbedResponse(response, ctx, MessagesCategory.PLAYER)
|
||||||
|
await cogResponser1.run()
|
||||||
|
except Exception:
|
||||||
|
print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}')
|
||||||
|
|
||||||
|
@slash_command(name="queue", description=helper.HELP_QUEUE)
|
||||||
|
async def queue(self, ctx: ApplicationContext,
|
||||||
|
page_number: Option(int, helper.SLASH_QUEUE_DESCRIPTION, min_value=1, default=1)) -> None:
|
||||||
|
if not self.__bot.listingSlash:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
await ctx.defer()
|
||||||
|
controller = QueueHandler(ctx, self.__bot)
|
||||||
|
|
||||||
|
# Change index 1 to 0
|
||||||
|
page_number -= 1
|
||||||
|
response = await controller.run(page_number)
|
||||||
|
|
||||||
|
cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.QUEUE)
|
||||||
|
await cogResponser.run()
|
||||||
|
except Exception:
|
||||||
|
print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}')
|
||||||
|
|
||||||
|
@slash_command(name="skip", description=helper.HELP_SKIP)
|
||||||
|
async def skip(self, ctx: ApplicationContext) -> None:
|
||||||
|
if not self.__bot.listingSlash:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
await ctx.defer()
|
||||||
|
controller = SkipHandler(ctx, self.__bot)
|
||||||
|
|
||||||
|
response = await controller.run()
|
||||||
|
cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.PLAYER)
|
||||||
|
await cogResponser.run()
|
||||||
|
except Exception:
|
||||||
|
print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}')
|
||||||
|
|
||||||
|
@slash_command(name='stop', description=helper.HELP_STOP)
|
||||||
|
async def stop(self, ctx: ApplicationContext) -> None:
|
||||||
|
if not self.__bot.listingSlash:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
await ctx.defer()
|
||||||
|
controller = StopHandler(ctx, self.__bot)
|
||||||
|
|
||||||
|
response = await controller.run()
|
||||||
|
cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.PLAYER)
|
||||||
|
await cogResponser.run()
|
||||||
|
except Exception:
|
||||||
|
print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}')
|
||||||
|
|
||||||
|
@slash_command(name='pause', description=helper.HELP_PAUSE)
|
||||||
|
async def pause(self, ctx: ApplicationContext) -> None:
|
||||||
|
if not self.__bot.listingSlash:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
await ctx.defer()
|
||||||
|
controller = PauseHandler(ctx, self.__bot)
|
||||||
|
|
||||||
|
response = await controller.run()
|
||||||
|
cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.PLAYER)
|
||||||
|
await cogResponser.run()
|
||||||
|
except Exception:
|
||||||
|
print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}')
|
||||||
|
|
||||||
|
@slash_command(name='resume', description=helper.HELP_RESUME)
|
||||||
|
async def resume(self, ctx: ApplicationContext) -> None:
|
||||||
|
if not self.__bot.listingSlash:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
await ctx.defer()
|
||||||
|
controller = ResumeHandler(ctx, self.__bot)
|
||||||
|
|
||||||
|
response = await controller.run()
|
||||||
|
cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.PLAYER)
|
||||||
|
await cogResponser.run()
|
||||||
|
except Exception:
|
||||||
|
print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}')
|
||||||
|
|
||||||
|
@slash_command(name='previous', description=helper.HELP_PREV)
|
||||||
|
async def previous(self, ctx: ApplicationContext) -> None:
|
||||||
|
if not self.__bot.listingSlash:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
await ctx.defer()
|
||||||
|
controller = PrevHandler(ctx, self.__bot)
|
||||||
|
|
||||||
|
response = await controller.run()
|
||||||
|
if response is not None:
|
||||||
|
cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.PLAYER)
|
||||||
|
await cogResponser.run()
|
||||||
|
except Exception:
|
||||||
|
print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}')
|
||||||
|
|
||||||
|
@slash_command(name='history', description=helper.HELP_HISTORY)
|
||||||
|
async def history(self, ctx: ApplicationContext) -> None:
|
||||||
|
if not self.__bot.listingSlash:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
await ctx.defer()
|
||||||
|
controller = HistoryHandler(ctx, self.__bot)
|
||||||
|
|
||||||
|
response = await controller.run()
|
||||||
|
cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.HISTORY)
|
||||||
|
await cogResponser.run()
|
||||||
|
except Exception:
|
||||||
|
print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}')
|
||||||
|
|
||||||
|
@slash_command(name='loop', description=helper.HELP_LOOP)
|
||||||
|
async def loop(self, ctx: ApplicationContext,
|
||||||
|
loop_type: Option(str, choices=[
|
||||||
|
OptionChoice(name='off', value='off'),
|
||||||
|
OptionChoice(name='one', value='one'),
|
||||||
|
OptionChoice(name='all', value='all')
|
||||||
|
])) -> None:
|
||||||
|
if not self.__bot.listingSlash:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
await ctx.defer()
|
||||||
|
controller = LoopHandler(ctx, self.__bot)
|
||||||
|
|
||||||
|
response = await controller.run(loop_type)
|
||||||
|
cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.LOOP)
|
||||||
|
await cogResponser.run()
|
||||||
|
except Exception:
|
||||||
|
print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}')
|
||||||
|
|
||||||
|
@slash_command(name='clear', description=helper.HELP_CLEAR)
|
||||||
|
async def clear(self, ctx: ApplicationContext) -> None:
|
||||||
|
if not self.__bot.listingSlash:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
await ctx.defer()
|
||||||
|
controller = ClearHandler(ctx, self.__bot)
|
||||||
|
|
||||||
|
response = await controller.run()
|
||||||
|
cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.PLAYER)
|
||||||
|
await cogResponser.run()
|
||||||
|
except Exception:
|
||||||
|
print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}')
|
||||||
|
|
||||||
|
@slash_command(name='now_playing', description=helper.HELP_NP)
|
||||||
|
async def now_playing(self, ctx: ApplicationContext) -> None:
|
||||||
|
if not self.__bot.listingSlash:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
await ctx.defer()
|
||||||
|
controller = NowPlayingHandler(ctx, self.__bot)
|
||||||
|
|
||||||
|
response = await controller.run()
|
||||||
|
cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.NOW_PLAYING)
|
||||||
|
await cogResponser.run()
|
||||||
|
except Exception:
|
||||||
|
print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}')
|
||||||
|
|
||||||
|
@slash_command(name='shuffle_songs', description=helper.HELP_SHUFFLE)
|
||||||
|
async def shuffle(self, ctx: ApplicationContext) -> None:
|
||||||
|
if not self.__bot.listingSlash:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
await ctx.defer()
|
||||||
|
controller = ShuffleHandler(ctx, self.__bot)
|
||||||
|
|
||||||
|
response = await controller.run()
|
||||||
|
cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.PLAYER)
|
||||||
|
await cogResponser.run()
|
||||||
|
except Exception:
|
||||||
|
print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}')
|
||||||
|
|
||||||
|
@slash_command(name='move_song', description=helper.SLASH_MOVE_HELP)
|
||||||
|
async def move(self, ctx: ApplicationContext,
|
||||||
|
from_pos: Option(int, "The position of song to move", min_value=1),
|
||||||
|
to_pos: Option(int, "The position to put the song, default 1", min_value=1, default=1)) -> None:
|
||||||
|
if not self.__bot.listingSlash:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
await ctx.defer()
|
||||||
|
if from_pos == 0:
|
||||||
|
from_pos = 1
|
||||||
|
|
||||||
|
controller = MoveHandler(ctx, self.__bot)
|
||||||
|
|
||||||
|
response = await controller.run(from_pos, to_pos)
|
||||||
|
cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.MANAGING_QUEUE)
|
||||||
|
await cogResponser.run()
|
||||||
|
except Exception:
|
||||||
|
print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}')
|
||||||
|
|
||||||
|
@slash_command(name='remove', description=helper.HELP_REMOVE)
|
||||||
|
async def remove(self, ctx: ApplicationContext,
|
||||||
|
position: Option(int, "The song position to remove", min_value=1)) -> None:
|
||||||
|
if not self.__bot.listingSlash:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
await ctx.defer()
|
||||||
|
controller = RemoveHandler(ctx, self.__bot)
|
||||||
|
|
||||||
|
response = await controller.run(position)
|
||||||
|
cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.MANAGING_QUEUE)
|
||||||
|
await cogResponser.run()
|
||||||
|
except Exception:
|
||||||
|
print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}')
|
||||||
|
|
||||||
|
@slash_command(name='reset', description=helper.HELP_RESET)
|
||||||
|
async def reset(self, ctx: ApplicationContext) -> None:
|
||||||
|
if not self.__bot.listingSlash:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
await ctx.defer()
|
||||||
|
controller = ResetHandler(ctx, self.__bot)
|
||||||
|
|
||||||
|
response = await controller.run()
|
||||||
|
cogResponser = SlashEmbedResponse(response, ctx, MessagesCategory.PLAYER)
|
||||||
|
await cogResponser.run()
|
||||||
|
except Exception:
|
||||||
|
print(f'[ERROR IN SLASH COMMAND] -> {traceback.format_exc()}')
|
||||||
|
|
||||||
|
|
||||||
|
def setup(bot):
|
||||||
|
bot.add_cog(SlashCommands(bot))
|
||||||
25
HEROKU.md
Normal file
25
HEROKU.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<h1 align="center">Configuring Heroku</h1>
|
||||||
|
|
||||||
|
> Heroku doesn't offer free services anymore
|
||||||
|
|
||||||
|
Nobody wants to run the Vulkan process on their machine, so we host the process on Heroku, <s>a cloud platform that contains free</s>.<br>
|
||||||
|
To configure the Vulkan to run in your Heroku account you will need to:
|
||||||
|
|
||||||
|
- Create an application project in Heroku.
|
||||||
|
- Configure the environment variables in your application.
|
||||||
|
- Add these buildpacks to your application:
|
||||||
|
|
||||||
|
```
|
||||||
|
- heroku/python
|
||||||
|
- https://github.com/jonathanong/heroku-buildpack-ffmpeg-latest.git
|
||||||
|
- https://github.com/xrisk/heroku-opus.git
|
||||||
|
```
|
||||||
|
The order shown above is exactly the upside-down order that Buildpacks should appear in Heroku.
|
||||||
|
- Set the heroku stack to be `heroku-20`. <br>
|
||||||
|
As shown in this issue: [Issue](https://github.com/RafaelSolVargas/Vulkan/issues/25) the heroku-buildpack doesn't work properly with the heroku application Stack set as above `heroku-20`.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
This [Youtube Video](https://www.youtube.com/watch?v=BPvg9bndP1U&ab_channel=TechWithTim) shows the process of hosting a Bot in Heroku.
|
||||||
|
|
||||||
|
You can also fork this project and set in Heroku to your application automatically deploy when your project receive a new commit, and then control when new versions of Vulkan become available to your Bot.
|
||||||
@@ -1,26 +1,39 @@
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import List
|
from Parallelism.Commands import VCommands
|
||||||
|
from multiprocessing import Queue
|
||||||
|
from typing import List, Union
|
||||||
from discord.ext.commands import Context
|
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 Config.Messages import Messages
|
||||||
|
from Music.VulkanBot import VulkanBot
|
||||||
from Handlers.HandlerResponse import HandlerResponse
|
from Handlers.HandlerResponse import HandlerResponse
|
||||||
from Config.Configs import Configs
|
from Config.Configs import VConfigs
|
||||||
from Config.Helper import Helper
|
from Config.Helper import Helper
|
||||||
from Views.Embeds import Embeds
|
from Config.Embeds import VEmbeds
|
||||||
|
|
||||||
|
|
||||||
class AbstractHandler(ABC):
|
class AbstractHandler(ABC):
|
||||||
def __init__(self, ctx: Context, bot: Client) -> None:
|
def __init__(self, ctx: Union[Context, Interaction], bot: VulkanBot) -> None:
|
||||||
self.__bot: Client = bot
|
self.__bot: VulkanBot = bot
|
||||||
self.__guild: Guild = ctx.guild
|
self.__guild: Guild = ctx.guild
|
||||||
self.__ctx: Context = ctx
|
self.__ctx: Context = ctx
|
||||||
self.__bot_user: ClientUser = self.__bot.user
|
self.__bot_user: ClientUser = self.__bot.user
|
||||||
self.__id = self.__bot_user.id
|
self.__id = self.__bot_user.id
|
||||||
self.__messages = Messages()
|
self.__messages = Messages()
|
||||||
self.__config = Configs()
|
self.__config = VConfigs()
|
||||||
self.__helper = Helper()
|
self.__helper = Helper()
|
||||||
self.__embeds = Embeds()
|
self.__embeds = VEmbeds()
|
||||||
self.__bot_member: Member = self.__get_member()
|
self.__bot_member: Member = self.__get_member()
|
||||||
|
if isinstance(ctx, Context):
|
||||||
|
self.__author = ctx.author
|
||||||
|
else:
|
||||||
|
self.__author = ctx.user
|
||||||
|
|
||||||
|
def putCommandInQueue(self, queue: Queue, command: VCommands) -> None:
|
||||||
|
try:
|
||||||
|
queue.put(command)
|
||||||
|
except Exception as e:
|
||||||
|
print(f'[ERROR PUTTING COMMAND IN QUEUE] -> {e}')
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def run(self) -> HandlerResponse:
|
async def run(self) -> HandlerResponse:
|
||||||
@@ -38,6 +51,10 @@ class AbstractHandler(ABC):
|
|||||||
def bot_user(self) -> ClientUser:
|
def bot_user(self) -> ClientUser:
|
||||||
return self.__bot_user
|
return self.__bot_user
|
||||||
|
|
||||||
|
@property
|
||||||
|
def author(self) -> User:
|
||||||
|
return self.__author
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def guild(self) -> Guild:
|
def guild(self) -> Guild:
|
||||||
return self.__guild
|
return self.__guild
|
||||||
@@ -47,7 +64,7 @@ class AbstractHandler(ABC):
|
|||||||
return self.__bot
|
return self.__bot
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def config(self) -> Configs:
|
def config(self) -> VConfigs:
|
||||||
return self.__config
|
return self.__config
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -59,11 +76,11 @@ class AbstractHandler(ABC):
|
|||||||
return self.__helper
|
return self.__helper
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ctx(self) -> Context:
|
def ctx(self) -> Union[Context, Interaction]:
|
||||||
return self.__ctx
|
return self.__ctx
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def embeds(self) -> Embeds:
|
def embeds(self) -> VEmbeds:
|
||||||
return self.__embeds
|
return self.__embeds
|
||||||
|
|
||||||
def __get_member(self) -> Member:
|
def __get_member(self) -> Member:
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
|
from typing import Union
|
||||||
|
from discord import Interaction
|
||||||
from discord.ext.commands import Context
|
from discord.ext.commands import Context
|
||||||
from discord import Client
|
from Music.VulkanBot import VulkanBot
|
||||||
from Handlers.AbstractHandler import AbstractHandler
|
from Handlers.AbstractHandler import AbstractHandler
|
||||||
from Handlers.HandlerResponse import HandlerResponse
|
from Handlers.HandlerResponse import HandlerResponse
|
||||||
from Parallelism.ProcessManager import ProcessManager
|
from Parallelism.ProcessInfo import ProcessInfo
|
||||||
|
|
||||||
|
|
||||||
class ClearHandler(AbstractHandler):
|
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)
|
super().__init__(ctx, bot)
|
||||||
|
|
||||||
async def run(self) -> HandlerResponse:
|
async def run(self) -> HandlerResponse:
|
||||||
# Get the current process of the guild
|
# Get the current process of the guild
|
||||||
processManager = ProcessManager()
|
processManager = self.config.getProcessManager()
|
||||||
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
processInfo: ProcessInfo = processManager.getRunningPlayerInfo(self.guild)
|
||||||
if processInfo:
|
if processInfo:
|
||||||
# Clear the playlist
|
# Clear the playlist
|
||||||
playlist = processInfo.getPlaylist()
|
playlist = processInfo.getPlaylist()
|
||||||
@@ -21,8 +23,8 @@ class ClearHandler(AbstractHandler):
|
|||||||
if acquired:
|
if acquired:
|
||||||
playlist.clear()
|
playlist.clear()
|
||||||
processLock.release()
|
processLock.release()
|
||||||
processLock.release()
|
embed = self.embeds.PLAYLIST_CLEAR()
|
||||||
return HandlerResponse(self.ctx)
|
return HandlerResponse(self.ctx, embed)
|
||||||
else:
|
else:
|
||||||
processManager.resetProcess(self.guild, self.ctx)
|
processManager.resetProcess(self.guild, self.ctx)
|
||||||
embed = self.embeds.PLAYER_RESTARTED()
|
embed = self.embeds.PLAYER_RESTARTED()
|
||||||
|
|||||||
@@ -1,24 +1,30 @@
|
|||||||
from typing import Union
|
from typing import Union
|
||||||
from discord.ext.commands import Context
|
from discord.ext.commands import Context
|
||||||
from Config.Exceptions import VulkanError
|
from Config.Exceptions import VulkanError
|
||||||
from discord import Embed
|
from discord import Embed, Interaction
|
||||||
|
from UI.Views.AbstractView import AbstractView
|
||||||
|
|
||||||
|
|
||||||
class HandlerResponse:
|
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, view=None) -> None:
|
||||||
self.__ctx: Context = ctx
|
self.__ctx: Context = ctx
|
||||||
self.__error: VulkanError = error
|
self.__error: VulkanError = error
|
||||||
self.__embed: Embed = embed
|
self.__embed: Embed = embed
|
||||||
self.__success = False if error else True
|
self.__success = False if error else True
|
||||||
|
self.__view = view
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ctx(self) -> Context:
|
def ctx(self) -> Union[Context, Interaction]:
|
||||||
return self.__ctx
|
return self.__ctx
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def embed(self) -> Union[Embed, None]:
|
def embed(self) -> Union[Embed, None]:
|
||||||
return self.__embed
|
return self.__embed
|
||||||
|
|
||||||
|
@property
|
||||||
|
def view(self) -> AbstractView:
|
||||||
|
return self.__view
|
||||||
|
|
||||||
def error(self) -> Union[VulkanError, None]:
|
def error(self) -> Union[VulkanError, None]:
|
||||||
return self.__error
|
return self.__error
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
from discord.ext.commands import Context
|
from discord.ext.commands import Context
|
||||||
from discord import Client
|
from Music.VulkanBot import VulkanBot
|
||||||
from Handlers.AbstractHandler import AbstractHandler
|
from Handlers.AbstractHandler import AbstractHandler
|
||||||
from Handlers.HandlerResponse import HandlerResponse
|
from Handlers.HandlerResponse import HandlerResponse
|
||||||
from Utils.Utils import Utils
|
from Utils.Utils import Utils
|
||||||
from Parallelism.ProcessManager import ProcessManager
|
from typing import Union
|
||||||
|
from discord import Interaction
|
||||||
|
|
||||||
|
|
||||||
class HistoryHandler(AbstractHandler):
|
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)
|
super().__init__(ctx, bot)
|
||||||
|
|
||||||
async def run(self) -> HandlerResponse:
|
async def run(self) -> HandlerResponse:
|
||||||
# Get the current process of the guild
|
# Get the current process of the guild
|
||||||
processManager = ProcessManager()
|
processManager = self.config.getProcessManager()
|
||||||
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
||||||
if processInfo:
|
if processInfo:
|
||||||
processLock = processInfo.getLock()
|
processLock = processInfo.getLock()
|
||||||
|
|||||||
80
Handlers/JumpMusicHandler.py
Normal file
80
Handlers/JumpMusicHandler.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
from typing import Union
|
||||||
|
from Config.Exceptions import BadCommandUsage, InvalidInput, NumberRequired, UnknownError, VulkanError
|
||||||
|
from Handlers.AbstractHandler import AbstractHandler
|
||||||
|
from discord.ext.commands import Context
|
||||||
|
from discord import Interaction
|
||||||
|
from Handlers.HandlerResponse import HandlerResponse
|
||||||
|
from Music.Playlist import Playlist
|
||||||
|
from Music.VulkanBot import VulkanBot
|
||||||
|
from Parallelism.Commands import VCommands, VCommandsType
|
||||||
|
|
||||||
|
|
||||||
|
class JumpMusicHandler(AbstractHandler):
|
||||||
|
"""Move a music from a specific position and play it directly"""
|
||||||
|
|
||||||
|
def __init__(self, ctx: Union[Context, Interaction], bot: VulkanBot) -> None:
|
||||||
|
super().__init__(ctx, bot)
|
||||||
|
|
||||||
|
async def run(self, musicPos: str) -> HandlerResponse:
|
||||||
|
processManager = self.config.getProcessManager()
|
||||||
|
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:
|
||||||
|
# Try to convert input to int
|
||||||
|
error = self.__validateInput(musicPos)
|
||||||
|
if error:
|
||||||
|
embed = self.embeds.ERROR_EMBED(error.message)
|
||||||
|
processLock.release()
|
||||||
|
return HandlerResponse(self.ctx, embed, error)
|
||||||
|
|
||||||
|
# Sanitize the input
|
||||||
|
playlist: Playlist = processInfo.getPlaylist()
|
||||||
|
musicPos = self.__sanitizeInput(playlist, musicPos)
|
||||||
|
|
||||||
|
# Validate the position
|
||||||
|
if not playlist.validate_position(musicPos):
|
||||||
|
error = InvalidInput()
|
||||||
|
embed = self.embeds.PLAYLIST_RANGE_ERROR()
|
||||||
|
processLock.release()
|
||||||
|
return HandlerResponse(self.ctx, embed, error)
|
||||||
|
try:
|
||||||
|
# Move the selected song
|
||||||
|
playlist.move_songs(musicPos, 1)
|
||||||
|
|
||||||
|
# Send a command to the player to skip the music
|
||||||
|
command = VCommands(VCommandsType.SKIP, None)
|
||||||
|
queue = processInfo.getQueueToPlayer()
|
||||||
|
self.putCommandInQueue(queue, command)
|
||||||
|
|
||||||
|
processLock.release()
|
||||||
|
return HandlerResponse(self.ctx)
|
||||||
|
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, position: str) -> Union[VulkanError, None]:
|
||||||
|
try:
|
||||||
|
position = int(position)
|
||||||
|
except:
|
||||||
|
return NumberRequired(self.messages.ERROR_NUMBER)
|
||||||
|
|
||||||
|
def __sanitizeInput(self, playlist: Playlist, position: int) -> int:
|
||||||
|
position = int(position)
|
||||||
|
|
||||||
|
if position == -1:
|
||||||
|
position = len(playlist.getSongs())
|
||||||
|
|
||||||
|
return position
|
||||||
@@ -1,18 +1,19 @@
|
|||||||
from discord.ext.commands import Context
|
from discord.ext.commands import Context
|
||||||
from discord import Client
|
from Music.VulkanBot import VulkanBot
|
||||||
from Handlers.AbstractHandler import AbstractHandler
|
from Handlers.AbstractHandler import AbstractHandler
|
||||||
from Handlers.HandlerResponse import HandlerResponse
|
from Handlers.HandlerResponse import HandlerResponse
|
||||||
from Config.Exceptions import BadCommandUsage
|
from Config.Exceptions import BadCommandUsage
|
||||||
from Parallelism.ProcessManager import ProcessManager
|
from typing import Union
|
||||||
|
from discord import Interaction
|
||||||
|
|
||||||
|
|
||||||
class LoopHandler(AbstractHandler):
|
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)
|
super().__init__(ctx, bot)
|
||||||
|
|
||||||
async def run(self, args: str) -> HandlerResponse:
|
async def run(self, args: str) -> HandlerResponse:
|
||||||
# Get the current process of the guild
|
# Get the current process of the guild
|
||||||
processManager = ProcessManager()
|
processManager = self.config.getProcessManager()
|
||||||
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
||||||
if not processInfo:
|
if not processInfo:
|
||||||
embed = self.embeds.NOT_PLAYING()
|
embed = self.embeds.NOT_PLAYING()
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
from typing import Union
|
from typing import Union
|
||||||
from discord.ext.commands import Context
|
from discord.ext.commands import Context
|
||||||
from discord import Client
|
from Music.VulkanBot import VulkanBot
|
||||||
from Handlers.AbstractHandler import AbstractHandler
|
from Handlers.AbstractHandler import AbstractHandler
|
||||||
from Handlers.HandlerResponse import HandlerResponse
|
from Handlers.HandlerResponse import HandlerResponse
|
||||||
from Config.Exceptions import BadCommandUsage, VulkanError, InvalidInput, NumberRequired, UnknownError
|
from Config.Exceptions import BadCommandUsage, VulkanError, InvalidInput, NumberRequired, UnknownError
|
||||||
from Music.Playlist import Playlist
|
from Music.Playlist import Playlist
|
||||||
from Parallelism.ProcessManager import ProcessManager
|
from typing import Union
|
||||||
|
from discord import Interaction
|
||||||
|
|
||||||
|
|
||||||
class MoveHandler(AbstractHandler):
|
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)
|
super().__init__(ctx, bot)
|
||||||
|
|
||||||
async def run(self, pos1: str, pos2: str) -> HandlerResponse:
|
async def run(self, pos1: str, pos2: str) -> HandlerResponse:
|
||||||
processManager = ProcessManager()
|
processManager = self.config.getProcessManager()
|
||||||
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
||||||
if not processInfo:
|
if not processInfo:
|
||||||
embed = self.embeds.NOT_PLAYING()
|
embed = self.embeds.NOT_PLAYING()
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
from discord.ext.commands import Context
|
from discord.ext.commands import Context
|
||||||
from discord import Client
|
|
||||||
from Handlers.AbstractHandler import AbstractHandler
|
from Handlers.AbstractHandler import AbstractHandler
|
||||||
from Handlers.HandlerResponse import HandlerResponse
|
from Handlers.HandlerResponse import HandlerResponse
|
||||||
|
from Music.VulkanBot import VulkanBot
|
||||||
from Utils.Cleaner import Cleaner
|
from Utils.Cleaner import Cleaner
|
||||||
from Parallelism.ProcessManager import ProcessManager
|
from typing import Union
|
||||||
|
from discord import Interaction
|
||||||
|
|
||||||
|
|
||||||
class NowPlayingHandler(AbstractHandler):
|
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)
|
super().__init__(ctx, bot)
|
||||||
self.__cleaner = Cleaner()
|
self.__cleaner = Cleaner()
|
||||||
|
|
||||||
async def run(self) -> HandlerResponse:
|
async def run(self) -> HandlerResponse:
|
||||||
# Get the current process of the guild
|
# Get the current process of the guild
|
||||||
processManager = ProcessManager()
|
processManager = self.config.getProcessManager()
|
||||||
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
||||||
if not processInfo:
|
if not processInfo:
|
||||||
embed = self.embeds.NOT_PLAYING()
|
embed = self.embeds.NOT_PLAYING()
|
||||||
|
|||||||
@@ -1,25 +1,32 @@
|
|||||||
from discord.ext.commands import Context
|
from discord.ext.commands import Context
|
||||||
from discord import Client
|
|
||||||
from Handlers.AbstractHandler import AbstractHandler
|
from Handlers.AbstractHandler import AbstractHandler
|
||||||
from Handlers.HandlerResponse import HandlerResponse
|
from Handlers.HandlerResponse import HandlerResponse
|
||||||
from Parallelism.ProcessManager import ProcessManager
|
|
||||||
from Parallelism.Commands import VCommands, VCommandsType
|
from Parallelism.Commands import VCommands, VCommandsType
|
||||||
|
from Parallelism.ProcessInfo import ProcessInfo, ProcessStatus
|
||||||
|
from Music.VulkanBot import VulkanBot
|
||||||
|
from typing import Union
|
||||||
|
from discord import Interaction
|
||||||
|
|
||||||
|
|
||||||
class PauseHandler(AbstractHandler):
|
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)
|
super().__init__(ctx, bot)
|
||||||
|
|
||||||
async def run(self) -> HandlerResponse:
|
async def run(self) -> HandlerResponse:
|
||||||
processManager = ProcessManager()
|
processManager = self.config.getProcessManager()
|
||||||
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
processInfo: ProcessInfo = processManager.getRunningPlayerInfo(self.guild)
|
||||||
if processInfo:
|
if processInfo:
|
||||||
|
if processInfo.getStatus() == ProcessStatus.SLEEPING:
|
||||||
|
embed = self.embeds.NOT_PLAYING()
|
||||||
|
return HandlerResponse(self.ctx, embed)
|
||||||
|
|
||||||
# Send Pause command to be execute by player process
|
# Send Pause command to be execute by player process
|
||||||
command = VCommands(VCommandsType.PAUSE, None)
|
command = VCommands(VCommandsType.PAUSE, None)
|
||||||
queue = processInfo.getQueue()
|
queue = processInfo.getQueueToPlayer()
|
||||||
queue.put(command)
|
self.putCommandInQueue(queue, command)
|
||||||
|
|
||||||
return HandlerResponse(self.ctx)
|
embed = self.embeds.PLAYER_PAUSED()
|
||||||
|
return HandlerResponse(self.ctx, embed)
|
||||||
else:
|
else:
|
||||||
embed = self.embeds.NOT_PLAYING()
|
embed = self.embeds.NOT_PLAYING()
|
||||||
return HandlerResponse(self.ctx, embed)
|
return HandlerResponse(self.ctx, embed)
|
||||||
|
|||||||
@@ -1,34 +1,35 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
import traceback
|
||||||
from typing import List
|
from typing import List
|
||||||
from Config.Exceptions import DownloadingError, InvalidInput, VulkanError
|
from Config.Exceptions import DownloadingError, InvalidInput, VulkanError
|
||||||
from discord.ext.commands import Context
|
from discord.ext.commands import Context
|
||||||
from discord import Client
|
|
||||||
from Handlers.AbstractHandler import AbstractHandler
|
from Handlers.AbstractHandler import AbstractHandler
|
||||||
from Config.Exceptions import ImpossibleMove, UnknownError
|
from Config.Exceptions import ImpossibleMove, UnknownError
|
||||||
from Handlers.HandlerResponse import HandlerResponse
|
from Handlers.HandlerResponse import HandlerResponse
|
||||||
from Music.Downloader import Downloader
|
from Music.Downloader import Downloader
|
||||||
from Music.Searcher import Searcher
|
from Music.Searcher import Searcher
|
||||||
from Music.Song import Song
|
from Music.Song import Song
|
||||||
from Parallelism.ProcessManager import ProcessManager
|
|
||||||
from Parallelism.ProcessInfo import ProcessInfo
|
from Parallelism.ProcessInfo import ProcessInfo
|
||||||
from Parallelism.Commands import VCommands, VCommandsType
|
from Parallelism.Commands import VCommands, VCommandsType
|
||||||
|
from Music.VulkanBot import VulkanBot
|
||||||
|
from typing import Union
|
||||||
|
from discord import Interaction
|
||||||
|
from Music.Playlist import Playlist
|
||||||
|
|
||||||
|
|
||||||
class PlayHandler(AbstractHandler):
|
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)
|
super().__init__(ctx, bot)
|
||||||
self.__searcher = Searcher()
|
self.__searcher = Searcher()
|
||||||
self.__down = Downloader()
|
self.__down = Downloader()
|
||||||
|
|
||||||
async def run(self, args: str) -> HandlerResponse:
|
async def run(self, track: str) -> HandlerResponse:
|
||||||
track = " ".join(args)
|
|
||||||
requester = self.ctx.author.name
|
requester = self.ctx.author.name
|
||||||
|
|
||||||
if not self.__isUserConnected():
|
if not self.__isUserConnected():
|
||||||
error = ImpossibleMove()
|
error = ImpossibleMove()
|
||||||
embed = self.embeds.NO_CHANNEL()
|
embed = self.embeds.NO_CHANNEL()
|
||||||
return HandlerResponse(self.ctx, embed, error)
|
return HandlerResponse(self.ctx, embed, error)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Search for musics and get the name of each song
|
# Search for musics and get the name of each song
|
||||||
musicsInfo = await self.__searcher.search(track)
|
musicsInfo = await self.__searcher.search(track)
|
||||||
@@ -36,9 +37,9 @@ class PlayHandler(AbstractHandler):
|
|||||||
raise InvalidInput(self.messages.INVALID_INPUT, self.messages.ERROR_TITLE)
|
raise InvalidInput(self.messages.INVALID_INPUT, self.messages.ERROR_TITLE)
|
||||||
|
|
||||||
# Get the process context for the current guild
|
# Get the process context for the current guild
|
||||||
processManager = ProcessManager()
|
processManager = self.config.getProcessManager()
|
||||||
processInfo = processManager.getPlayerInfo(self.guild, self.ctx)
|
processInfo = processManager.getOrCreatePlayerInfo(self.guild, self.ctx)
|
||||||
playlist = processInfo.getPlaylist()
|
playlist: Playlist = processInfo.getPlaylist()
|
||||||
process = processInfo.getProcess()
|
process = processInfo.getProcess()
|
||||||
if not process.is_alive(): # If process has not yet started, start
|
if not process.is_alive(): # If process has not yet started, start
|
||||||
process.start()
|
process.start()
|
||||||
@@ -72,9 +73,10 @@ class PlayHandler(AbstractHandler):
|
|||||||
playlist.add_song(song)
|
playlist.add_song(song)
|
||||||
# Release the acquired Lock
|
# Release the acquired Lock
|
||||||
processLock.release()
|
processLock.release()
|
||||||
queue = processInfo.getQueue()
|
queue = processInfo.getQueueToPlayer()
|
||||||
playCommand = VCommands(VCommandsType.PLAY, None)
|
playCommand = VCommands(VCommandsType.PLAY, None)
|
||||||
queue.put(playCommand)
|
self.putCommandInQueue(queue, playCommand)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
processManager.resetProcess(self.guild, self.ctx)
|
processManager.resetProcess(self.guild, self.ctx)
|
||||||
embed = self.embeds.PLAYER_RESTARTED()
|
embed = self.embeds.PLAYER_RESTARTED()
|
||||||
@@ -82,6 +84,12 @@ class PlayHandler(AbstractHandler):
|
|||||||
|
|
||||||
return response
|
return response
|
||||||
else: # If multiple songs added
|
else: # If multiple songs added
|
||||||
|
# If more than 10 songs, download and load the first 5 to start the play right away
|
||||||
|
if len(songs) > 10:
|
||||||
|
fiveFirstSongs = songs[0:5]
|
||||||
|
songs = songs[5:]
|
||||||
|
await self.__downloadSongsAndStore(fiveFirstSongs, processInfo)
|
||||||
|
|
||||||
# Trigger a task to download all songs and then store them in the process playlist
|
# Trigger a task to download all songs and then store them in the process playlist
|
||||||
asyncio.create_task(self.__downloadSongsAndStore(songs, processInfo))
|
asyncio.create_task(self.__downloadSongsAndStore(songs, processInfo))
|
||||||
|
|
||||||
@@ -92,11 +100,10 @@ class PlayHandler(AbstractHandler):
|
|||||||
embed = self.embeds.DOWNLOADING_ERROR()
|
embed = self.embeds.DOWNLOADING_ERROR()
|
||||||
return HandlerResponse(self.ctx, embed, error)
|
return HandlerResponse(self.ctx, embed, error)
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
|
print(f'ERROR IN PLAYHANDLER -> {traceback.format_exc()}', {type(error)})
|
||||||
if isinstance(error, VulkanError): # If error was already processed
|
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)
|
embed = self.embeds.CUSTOM_ERROR(error)
|
||||||
else:
|
else:
|
||||||
print(f'DEVELOPER NOTE -> PlayController Error: {error}, {type(error)}')
|
|
||||||
error = UnknownError()
|
error = UnknownError()
|
||||||
embed = self.embeds.UNKNOWN_ERROR()
|
embed = self.embeds.UNKNOWN_ERROR()
|
||||||
|
|
||||||
@@ -104,26 +111,32 @@ class PlayHandler(AbstractHandler):
|
|||||||
|
|
||||||
async def __downloadSongsAndStore(self, songs: List[Song], processInfo: ProcessInfo) -> None:
|
async def __downloadSongsAndStore(self, songs: List[Song], processInfo: ProcessInfo) -> None:
|
||||||
playlist = processInfo.getPlaylist()
|
playlist = processInfo.getPlaylist()
|
||||||
queue = processInfo.getQueue()
|
queue = processInfo.getQueueToPlayer()
|
||||||
playCommand = VCommands(VCommandsType.PLAY, None)
|
playCommand = VCommands(VCommandsType.PLAY, None)
|
||||||
|
tooManySongs = len(songs) > 100
|
||||||
|
|
||||||
# Trigger a task for each song to be downloaded
|
# Trigger a task for each song to be downloaded
|
||||||
tasks: List[asyncio.Task] = []
|
tasks: List[asyncio.Task] = []
|
||||||
for song in songs:
|
for index, song in enumerate(songs):
|
||||||
|
# If there is a lot of songs being downloaded, force a sleep to try resolve the Http Error 429 "To Many Requests"
|
||||||
|
# Trying to fix the issue https://github.com/RafaelSolVargas/Vulkan/issues/32
|
||||||
|
if tooManySongs and index % 3 == 0:
|
||||||
|
await asyncio.sleep(0.5)
|
||||||
task = asyncio.create_task(self.__down.download_song(song))
|
task = asyncio.create_task(self.__down.download_song(song))
|
||||||
tasks.append(task)
|
tasks.append(task)
|
||||||
|
|
||||||
# In the original order, await for the task and then if successfully downloaded add in the playlist
|
# In the original order, await for the task and then, if successfully downloaded, add to the playlist
|
||||||
processManager = ProcessManager()
|
processManager = self.config.getProcessManager()
|
||||||
for index, task in enumerate(tasks):
|
for index, task in enumerate(tasks):
|
||||||
await task
|
await task
|
||||||
song = songs[index]
|
song = songs[index]
|
||||||
if not song.problematic: # If downloaded add to the playlist and send play command
|
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()
|
processLock = processInfo.getLock()
|
||||||
acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
|
acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
|
||||||
if acquired:
|
if acquired:
|
||||||
playlist.add_song(song)
|
playlist.add_song(song)
|
||||||
queue.put(playCommand)
|
self.putCommandInQueue(queue, playCommand)
|
||||||
processLock.release()
|
processLock.release()
|
||||||
else:
|
else:
|
||||||
processManager.resetProcess(self.guild, self.ctx)
|
processManager.resetProcess(self.guild, self.ctx)
|
||||||
|
|||||||
@@ -1,19 +1,25 @@
|
|||||||
from discord.ext.commands import Context
|
from discord.ext.commands import Context
|
||||||
from discord import Client
|
|
||||||
from Handlers.AbstractHandler import AbstractHandler
|
from Handlers.AbstractHandler import AbstractHandler
|
||||||
from Config.Exceptions import BadCommandUsage, ImpossibleMove
|
from Config.Exceptions import BadCommandUsage, ImpossibleMove
|
||||||
from Handlers.HandlerResponse import HandlerResponse
|
from Handlers.HandlerResponse import HandlerResponse
|
||||||
from Parallelism.ProcessManager import ProcessManager
|
|
||||||
from Parallelism.Commands import VCommands, VCommandsType
|
from Parallelism.Commands import VCommands, VCommandsType
|
||||||
|
from Music.VulkanBot import VulkanBot
|
||||||
|
from typing import Union
|
||||||
|
from discord import Interaction
|
||||||
|
|
||||||
|
|
||||||
class PrevHandler(AbstractHandler):
|
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)
|
super().__init__(ctx, bot)
|
||||||
|
|
||||||
async def run(self) -> HandlerResponse:
|
async def run(self) -> HandlerResponse:
|
||||||
processManager = ProcessManager()
|
if not self.__user_connected():
|
||||||
processInfo = processManager.getPlayerInfo(self.guild, self.ctx)
|
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:
|
if not processInfo:
|
||||||
embed = self.embeds.NOT_PLAYING()
|
embed = self.embeds.NOT_PLAYING()
|
||||||
error = BadCommandUsage()
|
error = BadCommandUsage()
|
||||||
@@ -25,11 +31,6 @@ class PrevHandler(AbstractHandler):
|
|||||||
embed = self.embeds.NOT_PREVIOUS_SONG()
|
embed = self.embeds.NOT_PREVIOUS_SONG()
|
||||||
return HandlerResponse(self.ctx, embed, error)
|
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():
|
if playlist.isLoopingAll() or playlist.isLoopingOne():
|
||||||
error = BadCommandUsage()
|
error = BadCommandUsage()
|
||||||
embed = self.embeds.FAIL_DUE_TO_LOOP_ON()
|
embed = self.embeds.FAIL_DUE_TO_LOOP_ON()
|
||||||
@@ -41,13 +42,15 @@ class PrevHandler(AbstractHandler):
|
|||||||
process.start()
|
process.start()
|
||||||
|
|
||||||
# Send a prev command, together with the user voice channel
|
# Send a prev command, together with the user voice channel
|
||||||
prevCommand = VCommands(VCommandsType.PREV, self.ctx.author.voice.channel.id)
|
prevCommand = VCommands(VCommandsType.PREV, self.author.voice.channel.id)
|
||||||
queue = processInfo.getQueue()
|
queue = processInfo.getQueueToPlayer()
|
||||||
queue.put(prevCommand)
|
self.putCommandInQueue(queue, prevCommand)
|
||||||
return HandlerResponse(self.ctx)
|
|
||||||
|
embed = self.embeds.RETURNING_SONG()
|
||||||
|
return HandlerResponse(self.ctx, embed)
|
||||||
|
|
||||||
def __user_connected(self) -> bool:
|
def __user_connected(self) -> bool:
|
||||||
if self.ctx.author.voice:
|
if self.author.voice:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -1,20 +1,28 @@
|
|||||||
from discord.ext.commands import Context
|
from discord.ext.commands import Context
|
||||||
from discord import Client
|
from Config.Exceptions import InvalidIndex
|
||||||
from Handlers.AbstractHandler import AbstractHandler
|
from Handlers.AbstractHandler import AbstractHandler
|
||||||
from Handlers.HandlerResponse import HandlerResponse
|
from Handlers.HandlerResponse import HandlerResponse
|
||||||
from Music.Downloader import Downloader
|
from Handlers.JumpMusicHandler import JumpMusicHandler
|
||||||
|
from Messages.MessagesCategory import MessagesCategory
|
||||||
|
from UI.Views.BasicView import BasicView
|
||||||
from Utils.Utils import Utils
|
from Utils.Utils import Utils
|
||||||
from Parallelism.ProcessManager import ProcessManager
|
from Music.VulkanBot import VulkanBot
|
||||||
|
from Music.Song import Song
|
||||||
|
from Music.Playlist import Playlist
|
||||||
|
from typing import List, Union
|
||||||
|
from discord import Button, Interaction
|
||||||
|
from UI.Buttons.CallbackButton import CallbackButton
|
||||||
|
from UI.Buttons.PlaylistDropdown import PlaylistDropdown
|
||||||
|
from Config.Emojis import VEmojis
|
||||||
|
|
||||||
|
|
||||||
class QueueHandler(AbstractHandler):
|
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)
|
super().__init__(ctx, bot)
|
||||||
self.__down = Downloader()
|
|
||||||
|
|
||||||
async def run(self) -> HandlerResponse:
|
async def run(self, pageNumber=0) -> HandlerResponse:
|
||||||
# Retrieve the process of the guild
|
# Retrieve the process of the guild
|
||||||
processManager = ProcessManager()
|
processManager = self.config.getProcessManager()
|
||||||
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
||||||
if not processInfo: # If no process return empty list
|
if not processInfo: # If no process return empty list
|
||||||
embed = self.embeds.EMPTY_QUEUE()
|
embed = self.embeds.EMPTY_QUEUE()
|
||||||
@@ -24,7 +32,7 @@ class QueueHandler(AbstractHandler):
|
|||||||
processLock = processInfo.getLock()
|
processLock = processInfo.getLock()
|
||||||
acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
|
acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
|
||||||
if acquired:
|
if acquired:
|
||||||
playlist = processInfo.getPlaylist()
|
playlist: Playlist = processInfo.getPlaylist()
|
||||||
|
|
||||||
if playlist.isLoopingOne():
|
if playlist.isLoopingOne():
|
||||||
song = playlist.getCurrentSong()
|
song = playlist.getCurrentSong()
|
||||||
@@ -32,13 +40,26 @@ class QueueHandler(AbstractHandler):
|
|||||||
processLock.release() # Release the Lock
|
processLock.release() # Release the Lock
|
||||||
return HandlerResponse(self.ctx, embed)
|
return HandlerResponse(self.ctx, embed)
|
||||||
|
|
||||||
songs_preload = playlist.getSongsToPreload()
|
|
||||||
allSongs = playlist.getSongs()
|
allSongs = playlist.getSongs()
|
||||||
if len(songs_preload) == 0:
|
if len(allSongs) == 0:
|
||||||
embed = self.embeds.EMPTY_QUEUE()
|
embed = self.embeds.EMPTY_QUEUE()
|
||||||
processLock.release() # Release the Lock
|
processLock.release() # Release the Lock
|
||||||
return HandlerResponse(self.ctx, embed)
|
return HandlerResponse(self.ctx, embed)
|
||||||
|
|
||||||
|
songsPages = playlist.getSongsPages()
|
||||||
|
# Truncate the pageNumber to the closest value
|
||||||
|
if pageNumber < 0:
|
||||||
|
pageNumber = 0
|
||||||
|
elif pageNumber >= len(songsPages):
|
||||||
|
pageNumber = len(songsPages) - 1
|
||||||
|
|
||||||
|
# Select the page in queue to be printed
|
||||||
|
songs = songsPages[pageNumber]
|
||||||
|
# Create view for this embed
|
||||||
|
buttons = self.__createViewButtons(songsPages, pageNumber)
|
||||||
|
buttons.extend(self.__createViewJumpButtons(playlist))
|
||||||
|
queueView = BasicView(self.bot, buttons, self.config.QUEUE_VIEW_TIMEOUT)
|
||||||
|
|
||||||
if playlist.isLoopingAll():
|
if playlist.isLoopingAll():
|
||||||
title = self.messages.ALL_SONGS_LOOPING
|
title = self.messages.ALL_SONGS_LOOPING
|
||||||
else:
|
else:
|
||||||
@@ -48,17 +69,49 @@ class QueueHandler(AbstractHandler):
|
|||||||
for song in allSongs]))
|
for song in allSongs]))
|
||||||
total_songs = len(playlist.getSongs())
|
total_songs = len(playlist.getSongs())
|
||||||
|
|
||||||
text = f'📜 Queue length: {total_songs} | ⌛ Duration: `{total_time}` downloaded \n\n'
|
text = f'📜 Queue length: {total_songs} | Page Number: {pageNumber+1}/{len(songsPages)} | ⌛ Duration: `{total_time}` downloaded \n\n'
|
||||||
|
|
||||||
for pos, song in enumerate(songs_preload, start=1):
|
# To work get the correct index of all songs
|
||||||
song_name = song.title if song.title else self.messages.SONG_DOWNLOADING
|
startIndex = (pageNumber * self.config.MAX_SONGS_IN_PAGE) + 1
|
||||||
text += f"**`{pos}` - ** {song_name} - `{Utils.format_time(song.duration)}`\n"
|
for pos, song in enumerate(songs, start=startIndex):
|
||||||
|
song_name = song.title[:50] if song.title else self.messages.SONG_DOWNLOADING
|
||||||
|
|
||||||
|
songURL = ''
|
||||||
|
hasURL = False
|
||||||
|
if 'original_url' in song.info.keys():
|
||||||
|
hasURL = True
|
||||||
|
songURL = song.info['original_url']
|
||||||
|
elif 'webpage_url' in song.info.keys():
|
||||||
|
hasURL = True
|
||||||
|
songURL = song.info['webpage_url']
|
||||||
|
|
||||||
|
if hasURL:
|
||||||
|
text += f"**`{pos}` - ** [{song_name}]({songURL}) - `{Utils.format_time(song.duration)}`\n"
|
||||||
|
else:
|
||||||
|
text += f"**`{pos}` - ** {song_name} - `{Utils.format_time(song.duration)}`\n"
|
||||||
|
|
||||||
embed = self.embeds.QUEUE(title, text)
|
embed = self.embeds.QUEUE(title, text)
|
||||||
# Release the acquired Lock
|
# Release the acquired Lock
|
||||||
processLock.release()
|
processLock.release()
|
||||||
return HandlerResponse(self.ctx, embed)
|
return HandlerResponse(self.ctx, embed, view=queueView)
|
||||||
else:
|
else:
|
||||||
processManager.resetProcess(self.guild, self.ctx)
|
processManager.resetProcess(self.guild, self.ctx)
|
||||||
embed = self.embeds.PLAYER_RESTARTED()
|
embed = self.embeds.PLAYER_RESTARTED()
|
||||||
return HandlerResponse(self.ctx, embed)
|
return HandlerResponse(self.ctx, embed)
|
||||||
|
|
||||||
|
def __createViewButtons(self, songsPages: List[List[Song]], pageNumber: int) -> List[Button]:
|
||||||
|
buttons = []
|
||||||
|
if pageNumber > 0:
|
||||||
|
prevPageNumber = pageNumber - 1
|
||||||
|
buttons.append(CallbackButton(self.bot, self.run, VEmojis().BACK, self.ctx.channel,
|
||||||
|
self.guild.id, MessagesCategory.QUEUE, "Prev Page", pageNumber=prevPageNumber))
|
||||||
|
|
||||||
|
if pageNumber < len(songsPages) - 1:
|
||||||
|
nextPageNumber = pageNumber + 1
|
||||||
|
buttons.append(CallbackButton(self.bot, self.run, VEmojis().SKIP, self.ctx.channel,
|
||||||
|
self.guild.id, MessagesCategory.QUEUE, "Next Page", pageNumber=nextPageNumber))
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
def __createViewJumpButtons(self, playlist: Playlist) -> List[Button]:
|
||||||
|
return [PlaylistDropdown(self.bot, JumpMusicHandler, playlist, self.ctx.channel, self.guild.id, MessagesCategory.PLAYER)]
|
||||||
|
|||||||
@@ -1,29 +1,29 @@
|
|||||||
from typing import Union
|
|
||||||
from discord.ext.commands import Context
|
from discord.ext.commands import Context
|
||||||
from discord import Client
|
|
||||||
from Handlers.AbstractHandler import AbstractHandler
|
from Handlers.AbstractHandler import AbstractHandler
|
||||||
from Handlers.HandlerResponse import HandlerResponse
|
from Handlers.HandlerResponse import HandlerResponse
|
||||||
from Config.Exceptions import BadCommandUsage, VulkanError, ErrorRemoving, InvalidInput, NumberRequired
|
from Config.Exceptions import BadCommandUsage, VulkanError, ErrorRemoving, InvalidInput, NumberRequired
|
||||||
from Music.Playlist import Playlist
|
from Music.Playlist import Playlist
|
||||||
from Parallelism.ProcessManager import ProcessManager
|
from Music.VulkanBot import VulkanBot
|
||||||
|
from Parallelism.ProcessInfo import ProcessInfo
|
||||||
|
from typing import Union
|
||||||
|
from discord import Interaction
|
||||||
|
|
||||||
|
|
||||||
class RemoveHandler(AbstractHandler):
|
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)
|
super().__init__(ctx, bot)
|
||||||
|
|
||||||
async def run(self, position: str) -> HandlerResponse:
|
async def run(self, position: str) -> HandlerResponse:
|
||||||
# Get the current process of the guild
|
# Get the current process of the guild
|
||||||
processManager = ProcessManager()
|
processManager = self.config.getProcessManager()
|
||||||
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
processInfo: ProcessInfo = processManager.getRunningPlayerInfo(self.guild)
|
||||||
if not processInfo:
|
if not processInfo:
|
||||||
# Clear the playlist
|
|
||||||
embed = self.embeds.NOT_PLAYING()
|
embed = self.embeds.NOT_PLAYING()
|
||||||
error = BadCommandUsage()
|
error = BadCommandUsage()
|
||||||
return HandlerResponse(self.ctx, embed, error)
|
return HandlerResponse(self.ctx, embed, error)
|
||||||
|
|
||||||
playlist = processInfo.getPlaylist()
|
playlist = processInfo.getPlaylist()
|
||||||
if playlist.getCurrentSong() is None:
|
if playlist is None:
|
||||||
embed = self.embeds.NOT_PLAYING()
|
embed = self.embeds.NOT_PLAYING()
|
||||||
error = BadCommandUsage()
|
error = BadCommandUsage()
|
||||||
return HandlerResponse(self.ctx, embed, error)
|
return HandlerResponse(self.ctx, embed, error)
|
||||||
|
|||||||
@@ -1,23 +1,29 @@
|
|||||||
from discord.ext.commands import Context
|
from discord.ext.commands import Context
|
||||||
from discord import Client
|
|
||||||
from Handlers.AbstractHandler import AbstractHandler
|
from Handlers.AbstractHandler import AbstractHandler
|
||||||
from Handlers.HandlerResponse import HandlerResponse
|
from Handlers.HandlerResponse import HandlerResponse
|
||||||
from Parallelism.ProcessManager import ProcessManager
|
from Parallelism.ProcessInfo import ProcessInfo, ProcessStatus
|
||||||
from Parallelism.Commands import VCommands, VCommandsType
|
from Parallelism.Commands import VCommands, VCommandsType
|
||||||
|
from Music.VulkanBot import VulkanBot
|
||||||
|
from typing import Union
|
||||||
|
from discord import Interaction
|
||||||
|
|
||||||
|
|
||||||
class ResetHandler(AbstractHandler):
|
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)
|
super().__init__(ctx, bot)
|
||||||
|
|
||||||
async def run(self) -> HandlerResponse:
|
async def run(self) -> HandlerResponse:
|
||||||
# Get the current process of the guild
|
# Get the current process of the guild
|
||||||
processManager = ProcessManager()
|
processManager = self.config.getProcessManager()
|
||||||
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
processInfo: ProcessInfo = processManager.getRunningPlayerInfo(self.guild)
|
||||||
if processInfo:
|
if processInfo:
|
||||||
|
if processInfo.getStatus() == ProcessStatus.SLEEPING:
|
||||||
|
embed = self.embeds.NOT_PLAYING()
|
||||||
|
return HandlerResponse(self.ctx, embed)
|
||||||
|
|
||||||
command = VCommands(VCommandsType.RESET, None)
|
command = VCommands(VCommandsType.RESET, None)
|
||||||
queue = processInfo.getQueue()
|
queue = processInfo.getQueueToPlayer()
|
||||||
queue.put(command)
|
self.putCommandInQueue(queue, command)
|
||||||
|
|
||||||
return HandlerResponse(self.ctx)
|
return HandlerResponse(self.ctx)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,25 +1,32 @@
|
|||||||
from discord.ext.commands import Context
|
from discord.ext.commands import Context
|
||||||
from discord import Client
|
|
||||||
from Handlers.AbstractHandler import AbstractHandler
|
from Handlers.AbstractHandler import AbstractHandler
|
||||||
from Handlers.HandlerResponse import HandlerResponse
|
from Handlers.HandlerResponse import HandlerResponse
|
||||||
from Parallelism.ProcessManager import ProcessManager
|
from Parallelism.ProcessInfo import ProcessInfo, ProcessStatus
|
||||||
from Parallelism.Commands import VCommands, VCommandsType
|
from Parallelism.Commands import VCommands, VCommandsType
|
||||||
|
from Music.VulkanBot import VulkanBot
|
||||||
|
from typing import Union
|
||||||
|
from discord import Interaction
|
||||||
|
|
||||||
|
|
||||||
class ResumeHandler(AbstractHandler):
|
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)
|
super().__init__(ctx, bot)
|
||||||
|
|
||||||
async def run(self) -> HandlerResponse:
|
async def run(self) -> HandlerResponse:
|
||||||
processManager = ProcessManager()
|
processManager = self.config.getProcessManager()
|
||||||
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
processInfo: ProcessInfo = processManager.getRunningPlayerInfo(self.guild)
|
||||||
if processInfo:
|
if processInfo:
|
||||||
|
if processInfo.getStatus() == ProcessStatus.SLEEPING:
|
||||||
|
embed = self.embeds.NOT_PLAYING()
|
||||||
|
return HandlerResponse(self.ctx, embed)
|
||||||
|
|
||||||
# Send Resume command to be execute by player process
|
# Send Resume command to be execute by player process
|
||||||
command = VCommands(VCommandsType.RESUME, None)
|
command = VCommands(VCommandsType.RESUME, None)
|
||||||
queue = processInfo.getQueue()
|
queue = processInfo.getQueueToPlayer()
|
||||||
queue.put(command)
|
self.putCommandInQueue(queue, command)
|
||||||
|
|
||||||
return HandlerResponse(self.ctx)
|
embed = self.embeds.PLAYER_RESUMED()
|
||||||
|
return HandlerResponse(self.ctx, embed)
|
||||||
else:
|
else:
|
||||||
embed = self.embeds.NOT_PLAYING()
|
embed = self.embeds.NOT_PLAYING()
|
||||||
return HandlerResponse(self.ctx, embed)
|
return HandlerResponse(self.ctx, embed)
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
from discord.ext.commands import Context
|
from discord.ext.commands import Context
|
||||||
from discord import Client
|
|
||||||
from Handlers.AbstractHandler import AbstractHandler
|
from Handlers.AbstractHandler import AbstractHandler
|
||||||
from Handlers.HandlerResponse import HandlerResponse
|
from Handlers.HandlerResponse import HandlerResponse
|
||||||
from Config.Exceptions import UnknownError
|
from Config.Exceptions import UnknownError
|
||||||
from Parallelism.ProcessManager import ProcessManager
|
from Music.VulkanBot import VulkanBot
|
||||||
|
from typing import Union
|
||||||
|
from discord import Interaction
|
||||||
|
|
||||||
|
|
||||||
class ShuffleHandler(AbstractHandler):
|
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)
|
super().__init__(ctx, bot)
|
||||||
|
|
||||||
async def run(self) -> HandlerResponse:
|
async def run(self) -> HandlerResponse:
|
||||||
processManager = ProcessManager()
|
processManager = self.config.getProcessManager()
|
||||||
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
||||||
if processInfo:
|
if processInfo:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1,32 +1,44 @@
|
|||||||
from discord.ext.commands import Context
|
from discord.ext.commands import Context
|
||||||
from discord import Client
|
|
||||||
from Handlers.AbstractHandler import AbstractHandler
|
from Handlers.AbstractHandler import AbstractHandler
|
||||||
from Config.Exceptions import BadCommandUsage
|
from Config.Exceptions import BadCommandUsage, ImpossibleMove
|
||||||
from Handlers.HandlerResponse import HandlerResponse
|
from Handlers.HandlerResponse import HandlerResponse
|
||||||
from Parallelism.ProcessManager import ProcessManager
|
from Music.VulkanBot import VulkanBot
|
||||||
|
from Parallelism.ProcessInfo import ProcessInfo, ProcessStatus
|
||||||
from Parallelism.Commands import VCommands, VCommandsType
|
from Parallelism.Commands import VCommands, VCommandsType
|
||||||
|
from typing import Union
|
||||||
|
from discord import Interaction
|
||||||
|
|
||||||
|
|
||||||
class SkipHandler(AbstractHandler):
|
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)
|
super().__init__(ctx, bot)
|
||||||
|
|
||||||
async def run(self) -> HandlerResponse:
|
async def run(self) -> HandlerResponse:
|
||||||
processManager = ProcessManager()
|
if not self.__user_connected():
|
||||||
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
error = ImpossibleMove()
|
||||||
|
embed = self.embeds.NO_CHANNEL()
|
||||||
|
return HandlerResponse(self.ctx, embed, error)
|
||||||
|
|
||||||
|
processManager = self.config.getProcessManager()
|
||||||
|
processInfo: ProcessInfo = processManager.getRunningPlayerInfo(self.guild)
|
||||||
if processInfo: # Verify if there is a running process
|
if processInfo: # Verify if there is a running process
|
||||||
playlist = processInfo.getPlaylist()
|
if processInfo.getStatus() == ProcessStatus.SLEEPING:
|
||||||
if playlist.isLoopingOne():
|
embed = self.embeds.NOT_PLAYING()
|
||||||
embed = self.embeds.ERROR_DUE_LOOP_ONE_ON()
|
return HandlerResponse(self.ctx, embed)
|
||||||
error = BadCommandUsage()
|
|
||||||
return HandlerResponse(self.ctx, embed, error)
|
|
||||||
|
|
||||||
# Send a command to the player process to skip the music
|
# Send a command to the player process to skip the music
|
||||||
command = VCommands(VCommandsType.SKIP, None)
|
command = VCommands(VCommandsType.SKIP, None)
|
||||||
queue = processInfo.getQueue()
|
queue = processInfo.getQueueToPlayer()
|
||||||
queue.put(command)
|
self.putCommandInQueue(queue, command)
|
||||||
|
|
||||||
return HandlerResponse(self.ctx)
|
embed = self.embeds.SKIPPING_SONG()
|
||||||
|
return HandlerResponse(self.ctx, embed)
|
||||||
else:
|
else:
|
||||||
embed = self.embeds.NOT_PLAYING()
|
embed = self.embeds.NOT_PLAYING()
|
||||||
return HandlerResponse(self.ctx, embed)
|
return HandlerResponse(self.ctx, embed)
|
||||||
|
|
||||||
|
def __user_connected(self) -> bool:
|
||||||
|
if self.author.voice:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|||||||
@@ -1,25 +1,32 @@
|
|||||||
from discord.ext.commands import Context
|
from discord.ext.commands import Context
|
||||||
from discord import Client
|
|
||||||
from Handlers.AbstractHandler import AbstractHandler
|
from Handlers.AbstractHandler import AbstractHandler
|
||||||
from Handlers.HandlerResponse import HandlerResponse
|
from Handlers.HandlerResponse import HandlerResponse
|
||||||
from Parallelism.ProcessManager import ProcessManager
|
from Music.VulkanBot import VulkanBot
|
||||||
|
from Parallelism.ProcessInfo import ProcessInfo, ProcessStatus
|
||||||
from Parallelism.Commands import VCommands, VCommandsType
|
from Parallelism.Commands import VCommands, VCommandsType
|
||||||
|
from typing import Union
|
||||||
|
from discord import Interaction
|
||||||
|
|
||||||
|
|
||||||
class StopHandler(AbstractHandler):
|
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)
|
super().__init__(ctx, bot)
|
||||||
|
|
||||||
async def run(self) -> HandlerResponse:
|
async def run(self) -> HandlerResponse:
|
||||||
processManager = ProcessManager()
|
processManager = self.config.getProcessManager()
|
||||||
processInfo = processManager.getRunningPlayerInfo(self.guild)
|
processInfo: ProcessInfo = processManager.getRunningPlayerInfo(self.guild)
|
||||||
if processInfo:
|
if processInfo:
|
||||||
|
if processInfo.getStatus() == ProcessStatus.SLEEPING:
|
||||||
|
embed = self.embeds.NOT_PLAYING()
|
||||||
|
return HandlerResponse(self.ctx, embed)
|
||||||
|
|
||||||
# Send command to player process stop
|
# Send command to player process stop
|
||||||
command = VCommands(VCommandsType.STOP, None)
|
command = VCommands(VCommandsType.STOP, None)
|
||||||
queue = processInfo.getQueue()
|
queue = processInfo.getQueueToPlayer()
|
||||||
queue.put(command)
|
self.putCommandInQueue(queue, command)
|
||||||
|
|
||||||
return HandlerResponse(self.ctx)
|
embed = self.embeds.STOPPING_PLAYER()
|
||||||
|
return HandlerResponse(self.ctx, embed)
|
||||||
else:
|
else:
|
||||||
embed = self.embeds.NOT_PLAYING()
|
embed = self.embeds.NOT_PLAYING()
|
||||||
return HandlerResponse(self.ctx, embed)
|
return HandlerResponse(self.ctx, embed)
|
||||||
|
|||||||
39
Messages/DiscordMessages.py
Normal file
39
Messages/DiscordMessages.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
from discord import Message, WebhookMessage
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
|
||||||
|
class VAbstractMessage(ABC):
|
||||||
|
"""
|
||||||
|
Abstract class to allow create a pattern when dealing with multiple Discord
|
||||||
|
messages types, such as Interaction Messages and the standard discord messages
|
||||||
|
that contains two different ways of deletion
|
||||||
|
"""
|
||||||
|
@abstractmethod
|
||||||
|
async def delete(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class VWebHookMessage(VAbstractMessage):
|
||||||
|
"""
|
||||||
|
Holds a WebhookMessage instance
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, message: WebhookMessage) -> None:
|
||||||
|
self.__message = message
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
async def delete(self):
|
||||||
|
await self.__message.delete()
|
||||||
|
|
||||||
|
|
||||||
|
class VDefaultMessage(VAbstractMessage):
|
||||||
|
"""
|
||||||
|
Holds a Message instance, the basic Discord message type
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, message: Message) -> None:
|
||||||
|
self.__message = message
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
async def delete(self):
|
||||||
|
await self.__message.delete()
|
||||||
11
Messages/MessagesCategory.py
Normal file
11
Messages/MessagesCategory.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class MessagesCategory(Enum):
|
||||||
|
QUEUE = 1
|
||||||
|
HISTORY = 2
|
||||||
|
LOOP = 3
|
||||||
|
NOW_PLAYING = 4
|
||||||
|
PLAYER = 5
|
||||||
|
MANAGING_QUEUE = 6
|
||||||
|
OTHERS = 7
|
||||||
80
Messages/MessagesManager.py
Normal file
80
Messages/MessagesManager.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
from typing import Dict, List
|
||||||
|
from Config.Singleton import Singleton
|
||||||
|
from UI.Views.AbstractView import AbstractView
|
||||||
|
from Messages.MessagesCategory import MessagesCategory
|
||||||
|
from Messages.DiscordMessages import VAbstractMessage
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
|
class MessagesManager(Singleton):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
if not super().created:
|
||||||
|
# For each guild, and for each category, there will be a list of messages
|
||||||
|
self.__guildsMessages: Dict[int, Dict[MessagesCategory, List[VAbstractMessage]]] = {}
|
||||||
|
# Will, for each message, store the AbstractView that controls it
|
||||||
|
self.__messagesViews: Dict[VAbstractMessage, AbstractView] = {}
|
||||||
|
|
||||||
|
def addMessage(self, guildID: int, category: MessagesCategory, message: VAbstractMessage, view: AbstractView = None) -> None:
|
||||||
|
if message is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# If guild not exists create Dict
|
||||||
|
if guildID not in self.__guildsMessages.keys():
|
||||||
|
self.__guildsMessages[guildID] = {}
|
||||||
|
# If category not in guild yet, add
|
||||||
|
if category not in self.__guildsMessages[guildID].keys():
|
||||||
|
self.__guildsMessages[guildID][category] = []
|
||||||
|
|
||||||
|
sendedMessages = self.__guildsMessages[guildID][category]
|
||||||
|
if view is not None and isinstance(view, AbstractView):
|
||||||
|
self.__messagesViews[message] = view
|
||||||
|
sendedMessages.append(message)
|
||||||
|
|
||||||
|
async def addMessageAndClearPrevious(self, guildID: int, category: MessagesCategory, message: VAbstractMessage, view: AbstractView = None) -> None:
|
||||||
|
if message is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# If guild not exists create Dict
|
||||||
|
if guildID not in self.__guildsMessages.keys():
|
||||||
|
self.__guildsMessages[guildID] = {}
|
||||||
|
# If category not in guild yet, add
|
||||||
|
if category not in self.__guildsMessages[guildID].keys():
|
||||||
|
self.__guildsMessages[guildID][category] = []
|
||||||
|
|
||||||
|
sendedMessages = self.__guildsMessages[guildID][category]
|
||||||
|
|
||||||
|
# Delete sended all messages of this category
|
||||||
|
for previousMessage in sendedMessages:
|
||||||
|
await self.__deleteMessage(previousMessage)
|
||||||
|
|
||||||
|
# Create a new list with only the new message
|
||||||
|
self.__guildsMessages[guildID][category] = [message]
|
||||||
|
|
||||||
|
# Store the view of this message
|
||||||
|
if view is not None and isinstance(view, AbstractView):
|
||||||
|
self.__messagesViews[message] = view
|
||||||
|
|
||||||
|
async def clearMessagesOfCategory(self, guildID: int, category: MessagesCategory) -> None:
|
||||||
|
sendedMessages = self.__guildsMessages[guildID][category]
|
||||||
|
|
||||||
|
for message in sendedMessages:
|
||||||
|
self.__deleteMessage(message)
|
||||||
|
|
||||||
|
async def clearMessagesOfGuild(self, guildID: int) -> None:
|
||||||
|
categoriesMessages = self.__guildsMessages[guildID]
|
||||||
|
|
||||||
|
for category in categoriesMessages.keys():
|
||||||
|
for message in categoriesMessages[category]:
|
||||||
|
self.__deleteMessage(message)
|
||||||
|
|
||||||
|
async def __deleteMessage(self, message: VAbstractMessage) -> None:
|
||||||
|
try:
|
||||||
|
# If there is a view for this message delete the key
|
||||||
|
if message in self.__messagesViews.keys():
|
||||||
|
messageView = self.__messagesViews.pop(message)
|
||||||
|
messageView.stopView()
|
||||||
|
del messageView
|
||||||
|
|
||||||
|
await message.delete()
|
||||||
|
except Exception:
|
||||||
|
print(f'[ERROR DELETING MESSAGE] -> {traceback.format_exc()}')
|
||||||
45
Messages/Responses/AbstractCogResponse.py
Normal file
45
Messages/Responses/AbstractCogResponse.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from Handlers.HandlerResponse import HandlerResponse
|
||||||
|
from discord.ext.commands import Context
|
||||||
|
from discord import Message
|
||||||
|
from Messages.MessagesCategory import MessagesCategory
|
||||||
|
from Messages.MessagesManager import MessagesManager
|
||||||
|
from Music.VulkanBot import VulkanBot
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractCommandResponse(ABC):
|
||||||
|
def __init__(self, response: HandlerResponse, category: MessagesCategory) -> None:
|
||||||
|
self.__messagesManager = MessagesManager()
|
||||||
|
self.__response: HandlerResponse = response
|
||||||
|
self.__category: MessagesCategory = category
|
||||||
|
self.__context: Context = response.ctx
|
||||||
|
self.__message: Message = response.ctx.message
|
||||||
|
self.__bot: VulkanBot = response.ctx.bot
|
||||||
|
|
||||||
|
@property
|
||||||
|
def response(self) -> HandlerResponse:
|
||||||
|
return self.__response
|
||||||
|
|
||||||
|
@property
|
||||||
|
def category(self) -> MessagesCategory:
|
||||||
|
return self.__category
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bot(self) -> VulkanBot:
|
||||||
|
return self.__bot
|
||||||
|
|
||||||
|
@property
|
||||||
|
def message(self) -> Message:
|
||||||
|
return self.__message
|
||||||
|
|
||||||
|
@property
|
||||||
|
def context(self) -> Context:
|
||||||
|
return self.__context
|
||||||
|
|
||||||
|
@property
|
||||||
|
def manager(self) -> MessagesManager:
|
||||||
|
return self.__messagesManager
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def run(self, deleteLast: bool = True) -> None:
|
||||||
|
pass
|
||||||
29
Messages/Responses/EmbedCogResponse.py
Normal file
29
Messages/Responses/EmbedCogResponse.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
from Messages.Responses.AbstractCogResponse import AbstractCommandResponse
|
||||||
|
from Handlers.HandlerResponse import HandlerResponse
|
||||||
|
from Messages.MessagesCategory import MessagesCategory
|
||||||
|
from Messages.DiscordMessages import VAbstractMessage, VDefaultMessage
|
||||||
|
|
||||||
|
|
||||||
|
class EmbedCommandResponse(AbstractCommandResponse):
|
||||||
|
def __init__(self, response: HandlerResponse, category: MessagesCategory) -> None:
|
||||||
|
super().__init__(response, category)
|
||||||
|
|
||||||
|
async def run(self, deleteLast: bool = True) -> None:
|
||||||
|
message = None
|
||||||
|
# If the response has both embed and view to be sended
|
||||||
|
if self.response.embed and self.response.view:
|
||||||
|
message = await self.context.send(embed=self.response.embed, view=self.response.view)
|
||||||
|
# Set the view to contain the sended message
|
||||||
|
self.response.view.set_message(message)
|
||||||
|
|
||||||
|
# Or just a embed
|
||||||
|
elif self.response.embed:
|
||||||
|
message = await self.context.send(embed=self.response.embed)
|
||||||
|
|
||||||
|
if message:
|
||||||
|
vMessage: VAbstractMessage = VDefaultMessage(message)
|
||||||
|
# Only delete the previous message if this is not error and not forbidden by method caller
|
||||||
|
if deleteLast and self.response.success:
|
||||||
|
await self.manager.addMessageAndClearPrevious(self.context.guild.id, self.category, vMessage, self.response.view)
|
||||||
|
else:
|
||||||
|
self.manager.addMessage(self.context.guild.id, self.category, vMessage)
|
||||||
21
Messages/Responses/EmoteCogResponse.py
Normal file
21
Messages/Responses/EmoteCogResponse.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from Config.Emojis import VEmojis
|
||||||
|
from Messages.Responses.AbstractCogResponse import AbstractCommandResponse
|
||||||
|
from Handlers.HandlerResponse import HandlerResponse
|
||||||
|
from Messages.MessagesCategory import MessagesCategory
|
||||||
|
|
||||||
|
|
||||||
|
class EmoteCommandResponse(AbstractCommandResponse):
|
||||||
|
|
||||||
|
def __init__(self, response: HandlerResponse, category: MessagesCategory) -> None:
|
||||||
|
super().__init__(response, category)
|
||||||
|
self.__emojis = VEmojis()
|
||||||
|
|
||||||
|
async def run(self, deleteLast: bool = True) -> None:
|
||||||
|
# Now with Discord Interactions some commands are triggered without message
|
||||||
|
if (self.message is None):
|
||||||
|
return None
|
||||||
|
|
||||||
|
if self.response.success:
|
||||||
|
await self.message.add_reaction(self.__emojis.SUCCESS)
|
||||||
|
else:
|
||||||
|
await self.message.add_reaction(self.__emojis.ERROR)
|
||||||
35
Messages/Responses/SlashEmbedResponse.py
Normal file
35
Messages/Responses/SlashEmbedResponse.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
from Messages.Responses.AbstractCogResponse import AbstractCommandResponse
|
||||||
|
from Handlers.HandlerResponse import HandlerResponse
|
||||||
|
from Messages.MessagesCategory import MessagesCategory
|
||||||
|
from Messages.DiscordMessages import VAbstractMessage, VWebHookMessage
|
||||||
|
from discord import ApplicationContext
|
||||||
|
|
||||||
|
|
||||||
|
class SlashEmbedResponse(AbstractCommandResponse):
|
||||||
|
def __init__(self, response: HandlerResponse, ctx: ApplicationContext, category: MessagesCategory) -> None:
|
||||||
|
self.__ctx = ctx
|
||||||
|
super().__init__(response, category)
|
||||||
|
|
||||||
|
async def run(self, deleteLast: bool = True) -> None:
|
||||||
|
message = None
|
||||||
|
# If the response has both embed and view to send
|
||||||
|
if self.response.embed and self.response.view:
|
||||||
|
# Respond the Slash command and set the view to contain the sended message
|
||||||
|
message = await self.__ctx.send_followup(embed=self.response.embed, view=self.response.view)
|
||||||
|
self.response.view.set_message(message)
|
||||||
|
|
||||||
|
# If the response only has the embed then send the embed
|
||||||
|
elif self.response.embed:
|
||||||
|
message = await self.__ctx.send_followup(embed=self.response.embed)
|
||||||
|
else:
|
||||||
|
message = await self.__ctx.send_followup('Ok!')
|
||||||
|
|
||||||
|
# If any message was sended
|
||||||
|
if message:
|
||||||
|
# Convert the Discord message type to an Vulkan type
|
||||||
|
vMessage: VAbstractMessage = VWebHookMessage(message)
|
||||||
|
# Only delete the previous message if this is not error and not forbidden by method caller
|
||||||
|
if deleteLast and self.response.success:
|
||||||
|
await self.manager.addMessageAndClearPrevious(self.context.guild.id, self.category, vMessage, self.response.view)
|
||||||
|
else:
|
||||||
|
self.manager.addMessage(self.context.guild.id, self.category, vMessage)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from typing import List
|
from typing import List
|
||||||
from Config.Configs import Configs
|
from Config.Configs import VConfigs
|
||||||
from yt_dlp import YoutubeDL, DownloadError
|
from yt_dlp import YoutubeDL, DownloadError
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from Music.Song import Song
|
from Music.Song import Song
|
||||||
@@ -9,32 +9,35 @@ from Config.Exceptions import DownloadingError
|
|||||||
|
|
||||||
|
|
||||||
class Downloader:
|
class Downloader:
|
||||||
config = Configs()
|
config = VConfigs()
|
||||||
__YDL_OPTIONS = {'format': 'bestaudio/best',
|
__YDL_OPTIONS = {'format': 'bestaudio/best',
|
||||||
'default_search': 'auto',
|
'default_search': 'auto',
|
||||||
'playliststart': 0,
|
'playliststart': 0,
|
||||||
'extract_flat': False,
|
'extract_flat': False,
|
||||||
'playlistend': config.MAX_PLAYLIST_LENGTH,
|
'playlistend': config.MAX_PLAYLIST_LENGTH,
|
||||||
'quiet': True
|
'quiet': True,
|
||||||
|
'ignore_no_formats_error': True
|
||||||
}
|
}
|
||||||
__YDL_OPTIONS_EXTRACT = {'format': 'bestaudio/best',
|
__YDL_OPTIONS_EXTRACT = {'format': 'bestaudio/best',
|
||||||
'default_search': 'auto',
|
'default_search': 'auto',
|
||||||
'playliststart': 0,
|
'playliststart': 0,
|
||||||
'extract_flat': True,
|
'extract_flat': True,
|
||||||
'playlistend': config.MAX_PLAYLIST_LENGTH,
|
'playlistend': config.MAX_PLAYLIST_LENGTH,
|
||||||
'quiet': True
|
'quiet': True,
|
||||||
|
'ignore_no_formats_error': True
|
||||||
}
|
}
|
||||||
__YDL_OPTIONS_FORCE_EXTRACT = {'format': 'bestaudio/best',
|
__YDL_OPTIONS_FORCE_EXTRACT = {'format': 'bestaudio/best',
|
||||||
'default_search': 'auto',
|
'default_search': 'auto',
|
||||||
'playliststart': 0,
|
'playliststart': 0,
|
||||||
'extract_flat': False,
|
'extract_flat': False,
|
||||||
'playlistend': config.MAX_PLAYLIST_LENGTH,
|
'playlistend': config.MAX_PLAYLIST_LENGTH,
|
||||||
'quiet': True
|
'quiet': True,
|
||||||
|
'ignore_no_formats_error': True
|
||||||
}
|
}
|
||||||
__BASE_URL = 'https://www.youtube.com/watch?v={}'
|
__BASE_URL = 'https://www.youtube.com/watch?v={}'
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.__config = Configs()
|
self.__config = VConfigs()
|
||||||
self.__music_keys_only = ['resolution', 'fps', 'quality']
|
self.__music_keys_only = ['resolution', 'fps', 'quality']
|
||||||
self.__not_extracted_keys_only = ['ie_key']
|
self.__not_extracted_keys_only = ['ie_key']
|
||||||
self.__not_extracted_not_keys = ['entries']
|
self.__not_extracted_not_keys = ['entries']
|
||||||
@@ -53,8 +56,8 @@ class Downloader:
|
|||||||
song.finish_down(song_info)
|
song.finish_down(song_info)
|
||||||
return song
|
return song
|
||||||
# Convert yt_dlp error to my own error
|
# Convert yt_dlp error to my own error
|
||||||
except DownloadError:
|
except DownloadError as e:
|
||||||
raise DownloadingError()
|
raise DownloadingError(e.msg)
|
||||||
|
|
||||||
@run_async
|
@run_async
|
||||||
def extract_info(self, url: str) -> List[dict]:
|
def extract_info(self, url: str) -> List[dict]:
|
||||||
@@ -110,7 +113,7 @@ class Downloader:
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
except Exception as e: # Any type of error in download
|
except Exception as e: # Any type of error in download
|
||||||
print(f'DEVELOPER NOTE -> Error Downloading URL {e}')
|
print(f'DEVELOPER NOTE -> Error Downloading {url} -> {e}')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def download_song(self, song: Song) -> None:
|
async def download_song(self, song: Song) -> None:
|
||||||
@@ -118,12 +121,15 @@ class Downloader:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def __download_func(song: Song) -> None:
|
def __download_func(song: Song) -> None:
|
||||||
if Utils.is_url(song.identifier):
|
try:
|
||||||
song_info = self.__download_url(song.identifier)
|
if Utils.is_url(song.identifier):
|
||||||
else:
|
song_info = self.__download_url(song.identifier)
|
||||||
song_info = self.__download_title(song.identifier)
|
else:
|
||||||
|
song_info = self.__download_title(song.identifier)
|
||||||
|
|
||||||
song.finish_down(song_info)
|
song.finish_down(song_info)
|
||||||
|
except Exception as e:
|
||||||
|
print(f'DEVELOPER NOTE -> Error Downloading {song.identifier} -> {e}')
|
||||||
|
|
||||||
# Creating a loop task to download each song
|
# Creating a loop task to download each song
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
@@ -145,6 +151,8 @@ class Downloader:
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
if self.__is_multiple_musics(extracted_info):
|
if self.__is_multiple_musics(extracted_info):
|
||||||
|
if len(extracted_info['entries']) == 0:
|
||||||
|
return {}
|
||||||
return extracted_info['entries'][0]
|
return extracted_info['entries'][0]
|
||||||
else:
|
else:
|
||||||
print(f'DEVELOPER NOTE -> Failed to extract title {title}')
|
print(f'DEVELOPER NOTE -> Failed to extract title {title}')
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from collections import deque
|
from collections import deque
|
||||||
from typing import List
|
from typing import List
|
||||||
from Config.Configs import Configs
|
from Config.Configs import VConfigs
|
||||||
from Music.Song import Song
|
from Music.Song import Song
|
||||||
import random
|
import random
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ import random
|
|||||||
class Playlist:
|
class Playlist:
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.__configs = Configs()
|
self.__configs = VConfigs()
|
||||||
self.__queue = deque() # Store the musics to play
|
self.__queue = deque() # Store the musics to play
|
||||||
self.__songs_history = deque() # Store the musics played
|
self.__songs_history = deque() # Store the musics played
|
||||||
|
|
||||||
@@ -50,6 +50,15 @@ class Playlist:
|
|||||||
def getSongsToPreload(self) -> List[Song]:
|
def getSongsToPreload(self) -> List[Song]:
|
||||||
return list(self.__queue)[:self.__configs.MAX_PRELOAD_SONGS]
|
return list(self.__queue)[:self.__configs.MAX_PRELOAD_SONGS]
|
||||||
|
|
||||||
|
def getSongsPages(self) -> List[List[Song]]:
|
||||||
|
songsPages = []
|
||||||
|
for x in range(0, len(self.__queue), self.__configs.MAX_SONGS_IN_PAGE):
|
||||||
|
endIndex = x + self.__configs.MAX_SONGS_IN_PAGE
|
||||||
|
startIndex = x
|
||||||
|
songsPages.append(list(self.__queue)[startIndex:endIndex])
|
||||||
|
|
||||||
|
return songsPages
|
||||||
|
|
||||||
def __len__(self) -> int:
|
def __len__(self) -> int:
|
||||||
return len(self.__queue)
|
return len(self.__queue)
|
||||||
|
|
||||||
@@ -62,7 +71,7 @@ class Playlist:
|
|||||||
# Att played song info
|
# Att played song info
|
||||||
if played_song != None:
|
if played_song != None:
|
||||||
if not self.__looping_one and not self.__looping_all:
|
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)
|
self.__songs_history.appendleft(played_song)
|
||||||
|
|
||||||
if len(self.__songs_history) > self.__configs.MAX_SONGS_HISTORY:
|
if len(self.__songs_history) > self.__configs.MAX_SONGS_HISTORY:
|
||||||
@@ -79,7 +88,7 @@ class Playlist:
|
|||||||
self.__current = None
|
self.__current = None
|
||||||
return None
|
return None
|
||||||
|
|
||||||
self.__current = self.__queue.popleft()
|
self.__current: Song = self.__queue.popleft()
|
||||||
return self.__current
|
return self.__current
|
||||||
|
|
||||||
def prev_song(self) -> Song:
|
def prev_song(self) -> Song:
|
||||||
@@ -97,6 +106,10 @@ class Playlist:
|
|||||||
self.__queue.append(song)
|
self.__queue.append(song)
|
||||||
return song
|
return song
|
||||||
|
|
||||||
|
def add_song_start(self, song: Song) -> Song:
|
||||||
|
self.__queue.insert(0, song)
|
||||||
|
return song
|
||||||
|
|
||||||
def shuffle(self) -> None:
|
def shuffle(self) -> None:
|
||||||
random.shuffle(self.__queue)
|
random.shuffle(self.__queue)
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
class Song:
|
from time import time
|
||||||
|
|
||||||
|
|
||||||
|
class Song:
|
||||||
def __init__(self, identifier: str, playlist, requester: str) -> None:
|
def __init__(self, identifier: str, playlist, requester: str) -> None:
|
||||||
self.__identifier = identifier
|
self.__identifier = identifier
|
||||||
self.__info = {'requester': requester}
|
self.__info = {'requester': requester}
|
||||||
self.__problematic = False
|
self.__problematic = False
|
||||||
self.__playlist = playlist
|
self.__playlist = playlist
|
||||||
|
self.__downloadTime: int = time()
|
||||||
|
|
||||||
def finish_down(self, info: dict) -> None:
|
def finish_down(self, info: dict) -> None:
|
||||||
if info is None:
|
if info is None or info == {}:
|
||||||
self.destroy()
|
self.destroy()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
self.__downloadTime = time()
|
||||||
self.__useful_keys = ['duration',
|
self.__useful_keys = ['duration',
|
||||||
'title', 'webpage_url',
|
'title', 'webpage_url',
|
||||||
'channel', 'id', 'uploader',
|
'channel', 'id', 'uploader',
|
||||||
@@ -21,7 +25,8 @@ class Song:
|
|||||||
if key in info.keys():
|
if key in info.keys():
|
||||||
self.__info[key] = info[key]
|
self.__info[key] = info[key]
|
||||||
else:
|
else:
|
||||||
print(f'DEVELOPER NOTE -> {key} not found in info of music: {self.identifier}')
|
print(
|
||||||
|
f'DEVELOPER NOTE -> Required information [{key}] was not found in the music: {self.identifier}')
|
||||||
self.destroy()
|
self.destroy()
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -29,6 +34,16 @@ class Song:
|
|||||||
if key in info.keys():
|
if key in info.keys():
|
||||||
self.__info[key] = info[key]
|
self.__info[key] = info[key]
|
||||||
|
|
||||||
|
self.__cleanTitle()
|
||||||
|
|
||||||
|
def __cleanTitle(self) -> None:
|
||||||
|
self.__info['title'] = ''.join(char if char.isalnum() or char ==
|
||||||
|
' ' else ' ' for char in self.__info['title'])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def downloadTime(self) -> int:
|
||||||
|
return self.__downloadTime
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def source(self) -> str:
|
def source(self) -> str:
|
||||||
if 'url' in self.__info.keys():
|
if 'url' in self.__info.keys():
|
||||||
@@ -36,6 +51,10 @@ class Song:
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@source.setter
|
||||||
|
def source(self, value) -> None:
|
||||||
|
self.__info['url'] = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def title(self) -> str:
|
def title(self) -> str:
|
||||||
if 'title' in self.__info.keys():
|
if 'title' in self.__info.keys():
|
||||||
@@ -47,19 +66,23 @@ class Song:
|
|||||||
def duration(self) -> str:
|
def duration(self) -> str:
|
||||||
if 'duration' in self.__info.keys():
|
if 'duration' in self.__info.keys():
|
||||||
return self.__info['duration']
|
return self.__info['duration']
|
||||||
else:
|
else: # Default minimum duration
|
||||||
return 0.0
|
return 5.0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def identifier(self) -> str:
|
def identifier(self) -> str:
|
||||||
return self.__identifier
|
return self.__identifier
|
||||||
|
|
||||||
|
@identifier.setter
|
||||||
|
def identifier(self, value) -> None:
|
||||||
|
self.__identifier = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def problematic(self) -> bool:
|
def problematic(self) -> bool:
|
||||||
return self.__problematic
|
return self.__problematic
|
||||||
|
|
||||||
def destroy(self) -> None:
|
def destroy(self) -> None:
|
||||||
print(f'DEVELOPER NOTE -> Music self destroying {self.__identifier}')
|
print(f'MUSIC ERROR -> Music self destroying {self.__identifier}')
|
||||||
self.__problematic = True
|
self.__problematic = True
|
||||||
self.__playlist.destroy_song(self)
|
self.__playlist.destroy_song(self)
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ from spotipy import Spotify
|
|||||||
from spotipy.oauth2 import SpotifyClientCredentials
|
from spotipy.oauth2 import SpotifyClientCredentials
|
||||||
from spotipy.exceptions import SpotifyException
|
from spotipy.exceptions import SpotifyException
|
||||||
from Config.Exceptions import SpotifyError
|
from Config.Exceptions import SpotifyError
|
||||||
from Config.Configs import Configs
|
from Config.Configs import VConfigs
|
||||||
from Config.Messages import SpotifyMessages
|
from Config.Messages import SpotifyMessages
|
||||||
|
|
||||||
|
|
||||||
class SpotifySearch():
|
class SpotifySearch():
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.__messages = SpotifyMessages()
|
self.__messages = SpotifyMessages()
|
||||||
self.__config = Configs()
|
self.__config = VConfigs()
|
||||||
self.__connected = False
|
self.__connected = False
|
||||||
self.__connect()
|
self.__connect()
|
||||||
|
|
||||||
|
|||||||
81
Music/VulkanBot.py
Normal file
81
Music/VulkanBot.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
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, listingSlash: bool = False, *args, **kwargs):
|
||||||
|
"""If listing Slash is False then the process is just a Player Process, should not interact with discord commands"""
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.__listingSlash = listingSlash
|
||||||
|
self.__configs = VConfigs()
|
||||||
|
self.__messages = Messages()
|
||||||
|
self.__embeds = VEmbeds()
|
||||||
|
self.remove_command("help")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def listingSlash(self) -> bool:
|
||||||
|
return self.__listingSlash
|
||||||
|
|
||||||
|
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):
|
||||||
|
if self.__listingSlash:
|
||||||
|
print(self.__messages.STARTUP_MESSAGE)
|
||||||
|
await self.change_presence(status=Status.online, activity=Game(name=f"Vulkan | {self.__configs.BOT_PREFIX}help"))
|
||||||
|
if self.__listingSlash:
|
||||||
|
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
|
||||||
60
Music/VulkanInitializer.py
Normal file
60
Music/VulkanInitializer.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
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
|
||||||
|
bot = VulkanBot(listingSlash=True,
|
||||||
|
command_prefix=prefix,
|
||||||
|
pm_help=True,
|
||||||
|
case_insensitive=True,
|
||||||
|
intents=self.__intents)
|
||||||
|
else:
|
||||||
|
prefix = ''.join(choices(string.ascii_uppercase + string.digits, k=4))
|
||||||
|
bot = VulkanBot(listingSlash=False,
|
||||||
|
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(self.__config.COMMANDS_PATH):
|
||||||
|
if filename.endswith('.py'):
|
||||||
|
cogPath = f'{self.__config.COMMANDS_FOLDER_NAME}.{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(self.__config.COMMANDS_PATH):
|
||||||
|
if filename.endswith('.py'):
|
||||||
|
quant += 1
|
||||||
|
return quant
|
||||||
@@ -11,6 +11,9 @@ class VCommandsType(Enum):
|
|||||||
PLAY = 'Play'
|
PLAY = 'Play'
|
||||||
STOP = 'Stop'
|
STOP = 'Stop'
|
||||||
RESET = 'Reset'
|
RESET = 'Reset'
|
||||||
|
NOW_PLAYING = 'Now Playing'
|
||||||
|
TERMINATE = 'Terminate'
|
||||||
|
SLEEPING = 'Sleeping'
|
||||||
|
|
||||||
|
|
||||||
class VCommands:
|
class VCommands:
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from os import listdir
|
from time import time
|
||||||
from discord import Intents, User, Member, Message, Embed
|
from urllib.parse import parse_qs, urlparse
|
||||||
from asyncio import AbstractEventLoop, Semaphore
|
from Music.VulkanInitializer import VulkanInitializer
|
||||||
from multiprocessing import Process, Queue, RLock
|
from discord import User, Member, Message, VoiceClient
|
||||||
from threading import Lock, Thread
|
from asyncio import AbstractEventLoop, Semaphore, Queue
|
||||||
|
from multiprocessing import Process, RLock, Lock, Queue
|
||||||
|
from threading import Thread
|
||||||
from typing import Callable, List
|
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.Playlist import Playlist
|
||||||
from Music.Song import Song
|
from Music.Song import Song
|
||||||
from Config.Configs import Configs
|
from Config.Configs import VConfigs
|
||||||
from Config.Messages import Messages
|
from Config.Messages import Messages
|
||||||
from discord.ext.commands import Bot
|
from Music.VulkanBot import VulkanBot
|
||||||
from Views.Embeds import Embeds
|
from Music.Downloader import Downloader
|
||||||
|
from Config.Embeds import VEmbeds
|
||||||
from Parallelism.Commands import VCommands, VCommandsType
|
from Parallelism.Commands import VCommands, VCommandsType
|
||||||
|
|
||||||
|
|
||||||
@@ -21,7 +24,7 @@ class TimeoutClock:
|
|||||||
self.__task = loop.create_task(self.__executor())
|
self.__task = loop.create_task(self.__executor())
|
||||||
|
|
||||||
async def __executor(self):
|
async def __executor(self):
|
||||||
await asyncio.sleep(Configs().VC_TIMEOUT)
|
await asyncio.sleep(VConfigs().VC_TIMEOUT)
|
||||||
await self.__callback()
|
await self.__callback()
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self):
|
||||||
@@ -31,7 +34,7 @@ class TimeoutClock:
|
|||||||
class PlayerProcess(Process):
|
class PlayerProcess(Process):
|
||||||
"""Process that will play songs, receive commands from the main process by a Queue"""
|
"""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
|
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
|
Due to pickle serialization, no objects are stored, the values initialization are being made in the run method
|
||||||
@@ -40,7 +43,8 @@ class PlayerProcess(Process):
|
|||||||
# Synchronization objects
|
# Synchronization objects
|
||||||
self.__playlist: Playlist = playlist
|
self.__playlist: Playlist = playlist
|
||||||
self.__playlistLock: Lock = lock
|
self.__playlistLock: Lock = lock
|
||||||
self.__queue: Queue = queue
|
self.__queueReceive: Queue = queueToReceive
|
||||||
|
self.__queueSend: Queue = queueToSend
|
||||||
self.__semStopPlaying: Semaphore = None
|
self.__semStopPlaying: Semaphore = None
|
||||||
self.__loop: AbstractEventLoop = None
|
self.__loop: AbstractEventLoop = None
|
||||||
# Discord context ID
|
# Discord context ID
|
||||||
@@ -50,14 +54,15 @@ class PlayerProcess(Process):
|
|||||||
self.__authorID = authorID
|
self.__authorID = authorID
|
||||||
# All information of discord context will be retrieved directly with discord API
|
# All information of discord context will be retrieved directly with discord API
|
||||||
self.__guild: Guild = None
|
self.__guild: Guild = None
|
||||||
self.__bot: Client = None
|
self.__bot: VulkanBot = None
|
||||||
self.__voiceChannel: VoiceChannel = None
|
self.__voiceChannel: VoiceChannel = None
|
||||||
|
self.__voiceClient: VoiceClient = None
|
||||||
self.__textChannel: TextChannel = None
|
self.__textChannel: TextChannel = None
|
||||||
self.__author: User = None
|
self.__author: User = None
|
||||||
self.__botMember: Member = None
|
self.__botMember: Member = None
|
||||||
|
|
||||||
self.__configs: Configs = None
|
self.__configs: VConfigs = None
|
||||||
self.__embeds: Embeds = None
|
self.__embeds: VEmbeds = None
|
||||||
self.__messages: Messages = None
|
self.__messages: Messages = None
|
||||||
self.__messagesToDelete: List[Message] = []
|
self.__messagesToDelete: List[Message] = []
|
||||||
self.__playing = False
|
self.__playing = False
|
||||||
@@ -68,13 +73,15 @@ class PlayerProcess(Process):
|
|||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
"""Method called by process.start(), this will exec the actually _run method in a event loop"""
|
"""Method called by process.start(), this will exec the actually _run method in a event loop"""
|
||||||
try:
|
try:
|
||||||
print(f'Starting Process {self.name}')
|
print(f'Starting Player Process for Guild {self.name}')
|
||||||
self.__playerLock = RLock()
|
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.__messages = Messages()
|
||||||
self.__embeds = Embeds()
|
self.__embeds = VEmbeds()
|
||||||
|
self.__downloader = Downloader()
|
||||||
|
|
||||||
self.__semStopPlaying = Semaphore(0)
|
self.__semStopPlaying = Semaphore(0)
|
||||||
self.__loop.run_until_complete(self._run())
|
self.__loop.run_until_complete(self._run())
|
||||||
@@ -106,13 +113,25 @@ class PlayerProcess(Process):
|
|||||||
# In this point the process should finalize
|
# In this point the process should finalize
|
||||||
self.__timer.cancel()
|
self.__timer.cancel()
|
||||||
|
|
||||||
|
def __verifyIfIsPlaying(self) -> bool:
|
||||||
|
if self.__voiceClient is None:
|
||||||
|
return False
|
||||||
|
if not self.__voiceClient.is_connected():
|
||||||
|
return False
|
||||||
|
return self.__voiceClient.is_playing() or self.__voiceClient.is_paused()
|
||||||
|
|
||||||
async def __playPlaylistSongs(self) -> None:
|
async def __playPlaylistSongs(self) -> None:
|
||||||
|
"""If the player is not running trigger to play a new song"""
|
||||||
|
self.__playing = self.__verifyIfIsPlaying()
|
||||||
if not self.__playing:
|
if not self.__playing:
|
||||||
|
song = None
|
||||||
with self.__playlistLock:
|
with self.__playlistLock:
|
||||||
song = self.__playlist.next_song()
|
with self.__playerLock:
|
||||||
|
song = self.__playlist.next_song()
|
||||||
|
|
||||||
if song is not None:
|
if song is not None:
|
||||||
self.__loop.create_task(self.__playSong(song), name=f'Song {song.identifier}')
|
self.__loop.create_task(self.__playSong(song), name=f'Song {song.identifier}')
|
||||||
|
self.__playing = True
|
||||||
|
|
||||||
async def __playSong(self, song: Song) -> None:
|
async def __playSong(self, song: Song) -> None:
|
||||||
"""Function that will trigger the player to play the song"""
|
"""Function that will trigger the player to play the song"""
|
||||||
@@ -125,80 +144,133 @@ class PlayerProcess(Process):
|
|||||||
return self.__playNext(None)
|
return self.__playNext(None)
|
||||||
|
|
||||||
# If not connected, connect to bind channel
|
# If not connected, connect to bind channel
|
||||||
if self.__guild.voice_client is None:
|
if self.__voiceClient is None:
|
||||||
await self.__connectToVoiceChannel()
|
await self.__connectToVoiceChannel()
|
||||||
|
|
||||||
# If the player is already playing return
|
# If the voice channel disconnect for some reason
|
||||||
if self.__guild.voice_client.is_playing():
|
if not self.__voiceClient.is_connected():
|
||||||
|
print('[VOICE CHANNEL NOT NULL BUT DISCONNECTED, CONNECTING AGAIN]')
|
||||||
|
await self.__connectToVoiceChannel()
|
||||||
|
# If the player is connected and playing return the song to the playlist
|
||||||
|
elif self.__voiceClient.is_playing():
|
||||||
|
print('[SONG ALREADY PLAYING, RETURNING]')
|
||||||
|
self.__playlist.add_song_start(song)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
songStillAvailable = self.__verifyIfSongAvailable(song)
|
||||||
|
if not songStillAvailable:
|
||||||
|
print('[SONG NOT AVAILABLE ANYMORE, DOWNLOADING AGAIN]')
|
||||||
|
song = self.__downloadSongAgain(song)
|
||||||
|
|
||||||
self.__playing = True
|
self.__playing = True
|
||||||
self.__playingSong = song
|
self.__songPlaying = song
|
||||||
|
|
||||||
player = FFmpegPCMAudio(song.source, **self.FFMPEG_OPTIONS)
|
player = FFmpegPCMAudio(song.source, **self.FFMPEG_OPTIONS)
|
||||||
self.__guild.voice_client.play(player, after=lambda e: self.__playNext(e))
|
self.__voiceClient.play(player, after=lambda e: self.__playNext(e))
|
||||||
|
|
||||||
self.__timer.cancel()
|
self.__timer.cancel()
|
||||||
self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop)
|
self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop)
|
||||||
|
|
||||||
await self.__deletePrevNowPlaying()
|
nowPlayingCommand = VCommands(VCommandsType.NOW_PLAYING, song)
|
||||||
await self.__showNowPlaying()
|
self.__queueSend.put(nowPlayingCommand)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'[ERROR IN PLAY SONG] -> {e}, {type(e)}')
|
print(f'[ERROR IN PLAY SONG FUNCTION] -> {e}, {type(e)}')
|
||||||
self.__playNext(None)
|
self.__playNext(None)
|
||||||
finally:
|
finally:
|
||||||
self.__playerLock.release()
|
self.__playerLock.release()
|
||||||
|
|
||||||
def __playNext(self, error) -> None:
|
def __playNext(self, error) -> None:
|
||||||
with self.__playerLock:
|
if error is not None:
|
||||||
if self.__forceStop: # If it's forced to stop player
|
print(f'[ERROR PLAYING SONG] -> {error}')
|
||||||
self.__forceStop = False
|
with self.__playlistLock:
|
||||||
return 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()
|
song = self.__playlist.next_song()
|
||||||
|
|
||||||
if song is not None:
|
if song is not None:
|
||||||
self.__loop.create_task(self.__playSong(song), name=f'Song {song.identifier}')
|
self.__loop.create_task(self.__playSong(song), name=f'Song {song.identifier}')
|
||||||
else:
|
else:
|
||||||
with self.__playlistLock:
|
|
||||||
self.__playlist.loop_off()
|
self.__playlist.loop_off()
|
||||||
self.__playingSong = None
|
self.__songPlaying = None
|
||||||
self.__playing = False
|
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()
|
||||||
|
|
||||||
|
def __verifyIfSongAvailable(self, song: Song) -> bool:
|
||||||
|
"""Verify the song source to see if it's already expired"""
|
||||||
|
try:
|
||||||
|
parsedUrl = urlparse(song.source)
|
||||||
|
|
||||||
|
if 'expire' not in parsedUrl.query:
|
||||||
|
# If already passed 5 hours since the download
|
||||||
|
if song.downloadTime + 18000 < int(time()):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
# If the current time plus the song duration plus 10min exceeds the expirationValue
|
||||||
|
expireValue = parse_qs(parsedUrl.query)['expire'][0]
|
||||||
|
if int(time()) + song.duration + 600 > int(str(expireValue)):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f'[ERROR VERIFYING SONG AVAILABILITY] -> {e}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __downloadSongAgain(self, song: Song) -> Song:
|
||||||
|
"""Force a download to be executed again, one use case is when the song.source expired and needs to refresh"""
|
||||||
|
return self.__downloader.finish_one_song(song)
|
||||||
|
|
||||||
async def __playPrev(self, voiceChannelID: int) -> None:
|
async def __playPrev(self, voiceChannelID: int) -> None:
|
||||||
with self.__playlistLock:
|
with self.__playlistLock:
|
||||||
song = self.__playlist.prev_song()
|
song = self.__playlist.prev_song()
|
||||||
|
|
||||||
if song is not None:
|
with self.__playerLock:
|
||||||
if self.__guild.voice_client is None: # If not connect, connect to the user voice channel
|
if song is not None:
|
||||||
self.__voiceChannelID = voiceChannelID
|
# If not connect, connect to the user voice channel, may change the channel
|
||||||
self.__voiceChannel = self.__guild.get_channel(self.__voiceChannelID)
|
if self.__voiceClient is None or not self.__voiceClient.is_connected():
|
||||||
await self.__connectToVoiceChannel()
|
self.__voiceChannelID = voiceChannelID
|
||||||
|
self.__voiceChannel = self.__guild.get_channel(self.__voiceChannelID)
|
||||||
|
await self.__connectToVoiceChannel()
|
||||||
|
|
||||||
# If already playing, stop the current play
|
# If already playing, stop the current play
|
||||||
if self.__guild.voice_client.is_playing() or self.__guild.voice_client.is_paused():
|
if self.__verifyIfIsPlaying():
|
||||||
# Will forbidden next_song to execute after stopping current player
|
# Will forbidden next_song to execute after stopping current player
|
||||||
self.__forceStop = True
|
self.__forceStop = True
|
||||||
self.__guild.voice_client.stop()
|
self.__voiceClient.stop()
|
||||||
self.__playing = False
|
self.__playing = False
|
||||||
|
|
||||||
self.__loop.create_task(self.__playSong(song), name=f'Song {song.identifier}')
|
self.__loop.create_task(self.__playSong(song), name=f'Song {song.identifier}')
|
||||||
|
|
||||||
|
async def __restartCurrentSong(self) -> None:
|
||||||
|
song = self.__playlist.getCurrentSong()
|
||||||
|
if song is None:
|
||||||
|
song = self.__playlist.next_song()
|
||||||
|
if song is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.__loop.create_task(self.__playSong(song), name=f'Song {song.identifier}')
|
||||||
|
|
||||||
def __commandsReceiver(self) -> None:
|
def __commandsReceiver(self) -> None:
|
||||||
while True:
|
while True:
|
||||||
command: VCommands = self.__queue.get()
|
command: VCommands = self.__queueReceive.get()
|
||||||
type = command.getType()
|
type = command.getType()
|
||||||
args = command.getArgs()
|
args = command.getArgs()
|
||||||
|
print(f'Player Process {self.__guild.name} received command {type}')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.__playerLock.acquire()
|
self.__playerLock.acquire()
|
||||||
if type == VCommandsType.PAUSE:
|
if type == VCommandsType.PAUSE:
|
||||||
self.__pause()
|
self.__pause()
|
||||||
elif type == VCommandsType.RESUME:
|
elif type == VCommandsType.RESUME:
|
||||||
self.__resume()
|
asyncio.run_coroutine_threadsafe(self.__resume(), self.__loop)
|
||||||
elif type == VCommandsType.SKIP:
|
elif type == VCommandsType.SKIP:
|
||||||
self.__skip()
|
asyncio.run_coroutine_threadsafe(self.__skip(), self.__loop)
|
||||||
elif type == VCommandsType.PLAY:
|
elif type == VCommandsType.PLAY:
|
||||||
asyncio.run_coroutine_threadsafe(self.__playPlaylistSongs(), self.__loop)
|
asyncio.run_coroutine_threadsafe(self.__playPlaylistSongs(), self.__loop)
|
||||||
elif type == VCommandsType.PREV:
|
elif type == VCommandsType.PREV:
|
||||||
@@ -215,107 +287,131 @@ class PlayerProcess(Process):
|
|||||||
self.__playerLock.release()
|
self.__playerLock.release()
|
||||||
|
|
||||||
def __pause(self) -> None:
|
def __pause(self) -> None:
|
||||||
if self.__guild.voice_client is not None:
|
if self.__voiceClient is not None:
|
||||||
if self.__guild.voice_client.is_playing():
|
if self.__voiceClient.is_connected():
|
||||||
self.__guild.voice_client.pause()
|
if self.__voiceClient.is_playing():
|
||||||
|
self.__voiceClient.pause()
|
||||||
|
|
||||||
async def __reset(self) -> None:
|
async def __reset(self) -> None:
|
||||||
if self.__guild.voice_client is None:
|
if self.__voiceClient is None:
|
||||||
return
|
return
|
||||||
# Reset the bot
|
|
||||||
self.__guild.voice_client.stop()
|
if not self.__voiceClient.is_connected():
|
||||||
await self.__guild.voice_client.disconnect()
|
await self.__connectToVoiceChannel()
|
||||||
self.__playlist.clear()
|
if self.__songPlaying is not None:
|
||||||
self.__playlist.loop_off()
|
await self.__restartCurrentSong()
|
||||||
await self.__botMember.move_to(None)
|
|
||||||
# Release semaphore to finish the current player process
|
|
||||||
self.__semStopPlaying.release()
|
|
||||||
|
|
||||||
async def __stop(self) -> None:
|
async def __stop(self) -> None:
|
||||||
if self.__guild.voice_client is not None:
|
if self.__voiceClient is not None:
|
||||||
if self.__guild.voice_client.is_connected():
|
if self.__voiceClient.is_connected():
|
||||||
with self.__playlistLock:
|
with self.__playlistLock:
|
||||||
self.__playlist.clear()
|
|
||||||
self.__playlist.loop_off()
|
self.__playlist.loop_off()
|
||||||
|
self.__playlist.clear()
|
||||||
|
|
||||||
self.__guild.voice_client.stop()
|
# Send a command to the main process put this to sleep
|
||||||
self.__playingSong = None
|
sleepCommand = VCommands(VCommandsType.SLEEPING)
|
||||||
await self.__guild.voice_client.disconnect()
|
self.__queueSend.put(sleepCommand)
|
||||||
self.__semStopPlaying.release()
|
self.__voiceClient.stop()
|
||||||
|
await self.__voiceClient.disconnect()
|
||||||
|
|
||||||
def __resume(self) -> None:
|
self.__songPlaying = 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.__playing = False
|
||||||
self.__guild.voice_client.stop()
|
self.__voiceClient = None
|
||||||
|
self.__semStopPlaying.release()
|
||||||
|
# If the voiceClient is not None we finish things
|
||||||
|
else:
|
||||||
|
await self.__forceBotDisconnectAndStop()
|
||||||
|
|
||||||
async def __forceStop(self) -> None:
|
async def __resume(self) -> None:
|
||||||
# Lock to work with Player
|
# Lock to work with Player
|
||||||
with self.__playerLock:
|
with self.__playerLock:
|
||||||
if self.__guild.voice_client is None:
|
if self.__voiceClient is not None:
|
||||||
return
|
# If the player is paused then return to play
|
||||||
|
if self.__voiceClient.is_paused():
|
||||||
|
return self.__voiceClient.resume()
|
||||||
|
# If there is a current song but the voice client is not playing
|
||||||
|
elif self.__songPlaying is not None and not self.__voiceClient.is_playing():
|
||||||
|
await self.__playSong(self.__songPlaying)
|
||||||
|
|
||||||
self.__guild.voice_client.stop()
|
async def __skip(self) -> None:
|
||||||
await self.__guild.voice_client.disconnect()
|
self.__playing = self.__verifyIfIsPlaying()
|
||||||
|
# Lock to work with Player
|
||||||
|
with self.__playerLock:
|
||||||
|
if self.__playing:
|
||||||
|
self.__playing = False
|
||||||
|
self.__voiceClient.stop()
|
||||||
|
# If for some reason the Bot has disconnect but there is still songs to play
|
||||||
|
elif len(self.__playlist.getSongs()) > 0:
|
||||||
|
print('[RESTARTING CURRENT SONG]')
|
||||||
|
await self.__restartCurrentSong()
|
||||||
|
|
||||||
|
async def __forceBotDisconnectAndStop(self) -> None:
|
||||||
|
# Lock to work with Player
|
||||||
|
with self.__playerLock:
|
||||||
|
if self.__voiceClient is None:
|
||||||
|
return
|
||||||
|
self.__playing = False
|
||||||
|
self.__songPlaying = None
|
||||||
|
try:
|
||||||
|
self.__voiceClient.stop()
|
||||||
|
await self.__voiceClient.disconnect(force=True)
|
||||||
|
except Exception as e:
|
||||||
|
print(f'[ERROR FORCING BOT TO STOP] -> {e}')
|
||||||
|
finally:
|
||||||
|
self.__voiceClient = None
|
||||||
with self.__playlistLock:
|
with self.__playlistLock:
|
||||||
self.__playlist.clear()
|
self.__playlist.clear()
|
||||||
self.__playlist.loop_off()
|
self.__playlist.loop_off()
|
||||||
|
|
||||||
async def __createBotInstance(self) -> Client:
|
async def __createBotInstance(self) -> VulkanBot:
|
||||||
"""Load a new bot instance that should not be directly called.
|
"""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
|
initializer = VulkanInitializer(willListen=False)
|
||||||
"""
|
bot = initializer.getBot()
|
||||||
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
|
await bot.startBotCoro(self.__loop)
|
||||||
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)
|
await self.__ensureDiscordConnection(bot)
|
||||||
|
|
||||||
return bot
|
return bot
|
||||||
|
|
||||||
async def __timeoutHandler(self) -> None:
|
async def __timeoutHandler(self) -> None:
|
||||||
try:
|
try:
|
||||||
if self.__guild.voice_client is None:
|
# If there is not voiceClient return
|
||||||
|
if self.__voiceClient is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.__guild.voice_client.is_playing() or self.__guild.voice_client.is_paused():
|
# If the bot should not disconnect when alone
|
||||||
self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop)
|
if not VConfigs().SHOULD_AUTO_DISCONNECT_WHEN_ALONE:
|
||||||
|
return
|
||||||
|
|
||||||
elif self.__guild.voice_client.is_connected():
|
if self.__voiceClient.is_connected():
|
||||||
with self.__playerLock:
|
if self.__voiceClient.is_playing() or self.__voiceClient.is_paused():
|
||||||
with self.__playlistLock:
|
if not self.__isBotAloneInChannel(): # If bot is not alone continue to play
|
||||||
self.__playlist.clear()
|
self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop)
|
||||||
self.__playlist.loop_off()
|
return
|
||||||
self.__playing = False
|
|
||||||
await self.__guild.voice_client.disconnect()
|
# Finish the process
|
||||||
# Release semaphore to finish process
|
with self.__playerLock:
|
||||||
self.__semStopPlaying.release()
|
with self.__playlistLock:
|
||||||
|
self.__playlist.loop_off()
|
||||||
|
await self.__forceBotDisconnectAndStop()
|
||||||
|
# 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:
|
except Exception as e:
|
||||||
print(f'[Error in Timeout] -> {e}')
|
print(f'[ERROR IN TIMEOUT] -> {e}')
|
||||||
|
|
||||||
async def __ensureDiscordConnection(self, bot: Client) -> None:
|
def __isBotAloneInChannel(self) -> bool:
|
||||||
|
try:
|
||||||
|
if len(self.__voiceClient.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"""
|
"""Await in this point until connection to discord is established"""
|
||||||
guild = None
|
guild = None
|
||||||
while guild is None:
|
while guild is None:
|
||||||
@@ -324,7 +420,13 @@ class PlayerProcess(Process):
|
|||||||
|
|
||||||
async def __connectToVoiceChannel(self) -> bool:
|
async def __connectToVoiceChannel(self) -> bool:
|
||||||
try:
|
try:
|
||||||
await self.__voiceChannel.connect(reconnect=True, timeout=None)
|
print('[CONNECTING TO VOICE CHANNEL]')
|
||||||
|
if self.__voiceClient is not None:
|
||||||
|
try:
|
||||||
|
await self.__voiceClient.disconnect(force=True)
|
||||||
|
except Exception as e:
|
||||||
|
print(f'[ERROR FORCING DISCONNECT] -> {e}')
|
||||||
|
self.__voiceClient = await self.__voiceChannel.connect(reconnect=True, timeout=None)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'[ERROR CONNECTING TO VC] -> {e}')
|
print(f'[ERROR CONNECTING TO VC] -> {e}')
|
||||||
@@ -335,46 +437,3 @@ class PlayerProcess(Process):
|
|||||||
for member in guild_members:
|
for member in guild_members:
|
||||||
if member.id == self.__bot.user.id:
|
if member.id == self.__bot.user.id:
|
||||||
return member
|
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
|
|
||||||
|
|||||||
79
Parallelism/ProcessExecutor.py
Normal file
79
Parallelism/ProcessExecutor.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
from typing import List
|
||||||
|
from discord import Button, TextChannel
|
||||||
|
from discord.ui import View
|
||||||
|
from Config.Emojis import VEmojis
|
||||||
|
from Messages.MessagesCategory import MessagesCategory
|
||||||
|
from Music.VulkanBot import VulkanBot
|
||||||
|
from Parallelism.ProcessInfo import ProcessInfo
|
||||||
|
from Config.Messages import Messages
|
||||||
|
from Music.Song import Song
|
||||||
|
from Config.Embeds import VEmbeds
|
||||||
|
from UI.Buttons.HandlerButton import HandlerButton
|
||||||
|
from UI.Views.BasicView import BasicView
|
||||||
|
from Messages.MessagesManager import MessagesManager
|
||||||
|
from Handlers.PrevHandler import PrevHandler
|
||||||
|
from Handlers.PauseHandler import PauseHandler
|
||||||
|
from Handlers.SkipHandler import SkipHandler
|
||||||
|
from Handlers.StopHandler import StopHandler
|
||||||
|
from Handlers.ResumeHandler import ResumeHandler
|
||||||
|
from Handlers.LoopHandler import LoopHandler
|
||||||
|
from Handlers.QueueHandler import QueueHandler
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessCommandsExecutor:
|
||||||
|
def __init__(self, bot: VulkanBot, guildID: int) -> None:
|
||||||
|
self.__bot = bot
|
||||||
|
self.__guildID = guildID
|
||||||
|
self.__messagesManager = MessagesManager()
|
||||||
|
self.__messages = Messages()
|
||||||
|
self.__embeds = VEmbeds()
|
||||||
|
self.__emojis = VEmojis()
|
||||||
|
|
||||||
|
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)
|
||||||
|
channel = processInfo.getTextChannel()
|
||||||
|
view = self.__getPlayerView(channel)
|
||||||
|
# Send Message and add to the MessagesManager
|
||||||
|
message = await channel.send(embed=embed, view=view)
|
||||||
|
await self.__messagesManager.addMessageAndClearPrevious(self.__guildID, MessagesCategory.NOW_PLAYING, message, view)
|
||||||
|
|
||||||
|
# Set in the view the message witch contains the view
|
||||||
|
view.set_message(message=message)
|
||||||
|
|
||||||
|
def __getPlayerView(self, channel: TextChannel) -> View:
|
||||||
|
buttons = self.__getPlayerButtons(channel)
|
||||||
|
view = BasicView(self.__bot, buttons)
|
||||||
|
return view
|
||||||
|
|
||||||
|
def __getPlayerButtons(self, textChannel: TextChannel) -> List[Button]:
|
||||||
|
"""Create the Buttons to be inserted in the Player View"""
|
||||||
|
buttons: List[Button] = []
|
||||||
|
|
||||||
|
buttons.append(HandlerButton(self.__bot, PrevHandler, self.__emojis.BACK,
|
||||||
|
textChannel, self.__guildID, MessagesCategory.PLAYER, "Back"))
|
||||||
|
buttons.append(HandlerButton(self.__bot, PauseHandler, self.__emojis.PAUSE,
|
||||||
|
textChannel, self.__guildID, MessagesCategory.PLAYER, "Pause"))
|
||||||
|
buttons.append(HandlerButton(self.__bot, ResumeHandler, self.__emojis.PLAY,
|
||||||
|
textChannel, self.__guildID, MessagesCategory.PLAYER, "Play"))
|
||||||
|
buttons.append(HandlerButton(self.__bot, StopHandler, self.__emojis.STOP,
|
||||||
|
textChannel, self.__guildID, MessagesCategory.PLAYER, "Stop"))
|
||||||
|
buttons.append(HandlerButton(self.__bot, SkipHandler, self.__emojis.SKIP,
|
||||||
|
textChannel, self.__guildID, MessagesCategory.PLAYER, "Skip"))
|
||||||
|
buttons.append(HandlerButton(self.__bot, QueueHandler, self.__emojis.QUEUE,
|
||||||
|
textChannel, self.__guildID, MessagesCategory.QUEUE, "Songs"))
|
||||||
|
buttons.append(HandlerButton(self.__bot, LoopHandler, self.__emojis.LOOP_ONE,
|
||||||
|
textChannel, self.__guildID, MessagesCategory.LOOP, "Loop One", 'One'))
|
||||||
|
buttons.append(HandlerButton(self.__bot, LoopHandler, self.__emojis.LOOP_OFF,
|
||||||
|
textChannel, self.__guildID, MessagesCategory.LOOP, "Loop Off", 'Off'))
|
||||||
|
buttons.append(HandlerButton(self.__bot, LoopHandler, self.__emojis.LOOP_ALL,
|
||||||
|
textChannel, self.__guildID, MessagesCategory.LOOP, "Loop All", 'All'))
|
||||||
|
|
||||||
|
return buttons
|
||||||
@@ -1,29 +1,51 @@
|
|||||||
|
from enum import Enum
|
||||||
from multiprocessing import Process, Queue, Lock
|
from multiprocessing import Process, Queue, Lock
|
||||||
|
from discord import TextChannel
|
||||||
from Music.Playlist import Playlist
|
from Music.Playlist import Playlist
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessStatus(Enum):
|
||||||
|
RUNNING = 'Running'
|
||||||
|
SLEEPING = 'Sleeping'
|
||||||
|
|
||||||
|
|
||||||
class ProcessInfo:
|
class ProcessInfo:
|
||||||
"""
|
"""
|
||||||
Class to store the reference to all structures to maintain a player process
|
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.__process = process
|
||||||
self.__queue = queue
|
self.__queueToPlayer = queueToPlayer
|
||||||
|
self.__queueToMain = queueToMain
|
||||||
self.__playlist = playlist
|
self.__playlist = playlist
|
||||||
self.__lock = lock
|
self.__lock = lock
|
||||||
|
self.__textChannel = textChannel
|
||||||
|
self.__status = ProcessStatus.RUNNING
|
||||||
|
|
||||||
def setProcess(self, newProcess: Process) -> None:
|
def setProcess(self, newProcess: Process) -> None:
|
||||||
self.__process = newProcess
|
self.__process = newProcess
|
||||||
|
|
||||||
|
def getStatus(self) -> ProcessStatus:
|
||||||
|
return self.__status
|
||||||
|
|
||||||
|
def setStatus(self, status: ProcessStatus) -> None:
|
||||||
|
self.__status = status
|
||||||
|
|
||||||
def getProcess(self) -> Process:
|
def getProcess(self) -> Process:
|
||||||
return self.__process
|
return self.__process
|
||||||
|
|
||||||
def getQueue(self) -> Queue:
|
def getQueueToPlayer(self) -> Queue:
|
||||||
return self.__queue
|
return self.__queueToPlayer
|
||||||
|
|
||||||
|
def getQueueToMain(self) -> Queue:
|
||||||
|
return self.__queueToMain
|
||||||
|
|
||||||
def getPlaylist(self) -> Playlist:
|
def getPlaylist(self) -> Playlist:
|
||||||
return self.__playlist
|
return self.__playlist
|
||||||
|
|
||||||
def getLock(self) -> Lock:
|
def getLock(self) -> Lock:
|
||||||
return self.__lock
|
return self.__lock
|
||||||
|
|
||||||
|
def getTextChannel(self) -> TextChannel:
|
||||||
|
return self.__textChannel
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
from multiprocessing import Queue, Lock
|
import asyncio
|
||||||
|
from multiprocessing import Lock, Queue
|
||||||
from multiprocessing.managers import BaseManager, NamespaceProxy
|
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 Config.Singleton import Singleton
|
||||||
from discord import Guild
|
from discord import Guild, Interaction
|
||||||
from discord.ext.commands import Context
|
from discord.ext.commands import Context
|
||||||
|
from Parallelism.ProcessExecutor import ProcessCommandsExecutor
|
||||||
|
from Music.Song import Song
|
||||||
from Parallelism.PlayerProcess import PlayerProcess
|
from Parallelism.PlayerProcess import PlayerProcess
|
||||||
from Music.Playlist import Playlist
|
from Music.Playlist import Playlist
|
||||||
from Parallelism.ProcessInfo import ProcessInfo
|
from Parallelism.ProcessInfo import ProcessInfo, ProcessStatus
|
||||||
from Parallelism.Commands import VCommands, VCommandsType
|
from Parallelism.Commands import VCommands, VCommandsType
|
||||||
|
from Music.VulkanBot import VulkanBot
|
||||||
|
|
||||||
|
|
||||||
class ProcessManager(Singleton):
|
class ProcessManager(Singleton):
|
||||||
@@ -16,25 +22,28 @@ class ProcessManager(Singleton):
|
|||||||
Deal with the creation of shared memory
|
Deal with the creation of shared memory
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self, bot: VulkanBot = None) -> None:
|
||||||
if not super().created:
|
if not super().created:
|
||||||
|
self.__bot = bot
|
||||||
VManager.register('Playlist', Playlist)
|
VManager.register('Playlist', Playlist)
|
||||||
self.__manager = VManager()
|
self.__manager = VManager()
|
||||||
self.__manager.start()
|
self.__manager.start()
|
||||||
self.__playersProcess: Dict[Guild, ProcessInfo] = {}
|
self.__playersProcess: Dict[int, ProcessInfo] = {}
|
||||||
|
self.__playersListeners: Dict[int, Tuple[Thread, bool]] = {}
|
||||||
|
self.__playersCommandsExecutor: Dict[int, ProcessCommandsExecutor] = {}
|
||||||
|
|
||||||
def setPlayerContext(self, guild: Guild, context: ProcessInfo):
|
def setPlayerInfo(self, guild: Guild, info: ProcessInfo):
|
||||||
self.__playersProcess[guild.id] = context
|
self.__playersProcess[guild.id] = info
|
||||||
|
|
||||||
def getPlayerInfo(self, guild: Guild, context: Context) -> ProcessInfo:
|
def getOrCreatePlayerInfo(self, guild: Guild, context: Union[Context, Interaction]) -> ProcessInfo:
|
||||||
"""Return the process info for the guild, if not, create one"""
|
"""Return the process info for the guild, the user in context must be connected to a voice_channel"""
|
||||||
try:
|
try:
|
||||||
if guild.id not in self.__playersProcess.keys():
|
if guild.id not in self.__playersProcess.keys():
|
||||||
self.__playersProcess[guild.id] = self.__createProcessInfo(context)
|
self.__playersProcess[guild.id] = self.__createProcessInfo(guild, context)
|
||||||
else:
|
else:
|
||||||
# If the process has ended create a new one
|
# If the process has ended create a new one
|
||||||
if not self.__playersProcess[guild.id].getProcess().is_alive():
|
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]
|
return self.__playersProcess[guild.id]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -46,21 +55,22 @@ class ProcessManager(Singleton):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# Recreate the process keeping the playlist
|
# Recreate the process keeping the playlist
|
||||||
newProcessInfo = self.__recreateProcess(context)
|
newProcessInfo = self.__recreateProcess(guild, context)
|
||||||
newProcessInfo.getProcess().start() # Start the process
|
newProcessInfo.getProcess().start() # Start the process
|
||||||
# Send a command to start the play again
|
# Send a command to start the play again
|
||||||
playCommand = VCommands(VCommandsType.PLAY)
|
playCommand = VCommands(VCommandsType.PLAY)
|
||||||
newProcessInfo.getQueue().put(playCommand)
|
newProcessInfo.getQueueToPlayer().put(playCommand)
|
||||||
self.__playersProcess[guild.id] = newProcessInfo
|
self.__playersProcess[guild.id] = newProcessInfo
|
||||||
|
|
||||||
def getRunningPlayerInfo(self, guild: Guild) -> ProcessInfo:
|
def getRunningPlayerInfo(self, guild: Guild) -> ProcessInfo:
|
||||||
"""Return the process info for the guild, if not, return None"""
|
"""Return the process info for the guild, if not, return None"""
|
||||||
if guild.id not in self.__playersProcess.keys():
|
if guild.id not in self.__playersProcess.keys():
|
||||||
|
print('Process Info not found')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return self.__playersProcess[guild.id]
|
return self.__playersProcess[guild.id]
|
||||||
|
|
||||||
def __createProcessInfo(self, context: Context) -> ProcessInfo:
|
def __createProcessInfo(self, guild: Guild, context: Context) -> ProcessInfo:
|
||||||
guildID: int = context.guild.id
|
guildID: int = context.guild.id
|
||||||
textID: int = context.channel.id
|
textID: int = context.channel.id
|
||||||
voiceID: int = context.author.voice.channel.id
|
voiceID: int = context.author.voice.channel.id
|
||||||
@@ -68,30 +78,123 @@ class ProcessManager(Singleton):
|
|||||||
|
|
||||||
playlist: Playlist = self.__manager.Playlist()
|
playlist: Playlist = self.__manager.Playlist()
|
||||||
lock = Lock()
|
lock = Lock()
|
||||||
queue = Queue()
|
queueToListen = Queue()
|
||||||
process = PlayerProcess(context.guild.name, playlist, lock, queue,
|
queueToSend = Queue()
|
||||||
guildID, textID, voiceID, authorID)
|
process = PlayerProcess(context.guild.name, playlist, lock, queueToSend,
|
||||||
processInfo = ProcessInfo(process, queue, playlist, lock)
|
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.__playersCommandsExecutor[guildID] = ProcessCommandsExecutor(self.__bot, guildID)
|
||||||
|
|
||||||
return processInfo
|
return processInfo
|
||||||
|
|
||||||
def __recreateProcess(self, context: Context) -> ProcessInfo:
|
def __stopPossiblyRunningProcess(self, guild: Guild):
|
||||||
|
try:
|
||||||
|
if guild.id in self.__playersProcess.keys():
|
||||||
|
playerProcess = self.__playersProcess[guild.id]
|
||||||
|
process = playerProcess.getProcess()
|
||||||
|
process.close()
|
||||||
|
process.kill()
|
||||||
|
playerProcess.getQueueToMain().close()
|
||||||
|
playerProcess.getQueueToMain().join_thread()
|
||||||
|
playerProcess.getQueueToPlayer().close()
|
||||||
|
playerProcess.getQueueToPlayer().join_thread()
|
||||||
|
except Exception as e:
|
||||||
|
print(f'[ERROR STOPPING PROCESS] -> {e}')
|
||||||
|
|
||||||
|
def __recreateProcess(self, guild: Guild, context: Union[Context, Interaction]) -> ProcessInfo:
|
||||||
"""Create a new process info using previous playlist"""
|
"""Create a new process info using previous playlist"""
|
||||||
|
self.__stopPossiblyRunningProcess(guild)
|
||||||
|
|
||||||
guildID: int = context.guild.id
|
guildID: int = context.guild.id
|
||||||
textID: int = context.channel.id
|
textID: int = context.channel.id
|
||||||
voiceID: int = context.author.voice.channel.id
|
if isinstance(context, Interaction):
|
||||||
authorID: int = context.author.id
|
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()
|
playlist: Playlist = self.__playersProcess[guildID].getPlaylist()
|
||||||
lock = Lock()
|
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,
|
# Create a Thread to listen for the queue coming from the Player Process, this will redirect the Queue to a async
|
||||||
guildID, textID, voiceID, authorID)
|
thread = Thread(target=self.__listenToCommands,
|
||||||
processInfo = ProcessInfo(process, queue, playlist, lock)
|
args=(queueToListen, guild), daemon=True)
|
||||||
|
self.__playersListeners[guildID] = (thread, False)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
return processInfo
|
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.__playersCommandsExecutor[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()
|
||||||
|
# Set the status of this process as sleeping, only the playlist object remains
|
||||||
|
self.__playersProcess[guildID].setStatus(ProcessStatus.SLEEPING)
|
||||||
|
|
||||||
|
async def showNowPlaying(self, guildID: int, song: Song) -> None:
|
||||||
|
commandExecutor = self.__playersCommandsExecutor[guildID]
|
||||||
|
processInfo = self.__playersProcess[guildID]
|
||||||
|
await commandExecutor.sendNowPlaying(processInfo, song)
|
||||||
|
|
||||||
|
|
||||||
class VManager(BaseManager):
|
class VManager(BaseManager):
|
||||||
pass
|
pass
|
||||||
|
|||||||
118
README.md
118
README.md
@@ -1,68 +1,58 @@
|
|||||||
# **Vulkan**
|
<h1 align="center">Vulkan</h1>
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
|
|
||||||
# **Music**
|
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.
|
||||||
- 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
|
|
||||||
|
|
||||||
### Commands
|
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.
|
||||||
```!play [title, spotify_url, youtube_url, deezer_url]``` - Start playing song
|
|
||||||
|
|
||||||
```!resume``` - Resume the song player
|
|
||||||
|
|
||||||
```!pause``` - Pause the song player
|
<p align="center">
|
||||||
|
<img src="./Assets/playermenu.jpg" />
|
||||||
|
</p>
|
||||||
|
|
||||||
```!skip``` - Skip the currently playing song
|
|
||||||
|
|
||||||
```!prev``` - Return to play the previous song
|
# **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 contains buttons to shortcut some commands.
|
||||||
|
- Support for the new Discord Slash commands.
|
||||||
|
- Search for all musics in Queue using buttons.
|
||||||
|
- Shortcut the playing of one song using dropdown menu.
|
||||||
|
- Manage the loop of one or all playing musics.
|
||||||
|
- Manage the order and remove musics from the queue.
|
||||||
|
- Shuffle the musics queue order.
|
||||||
|
- Automatically clean the sended messages so it doesn't fill up your server.
|
||||||
|
|
||||||
```!stop``` - Stop the playing of musics
|
|
||||||
|
|
||||||
```!queue``` - Show the musics list in queue
|
<p align="center">
|
||||||
|
<img src="./Assets/vulkancommands.jpg" />
|
||||||
|
</p>
|
||||||
|
|
||||||
```!history``` - Show the played songs list
|
|
||||||
|
|
||||||
```!loop [one, all, off]``` - Control the loop of songs
|
<p align="center">
|
||||||
|
<img src="./Assets/queuemessage.jpg" />
|
||||||
```!shuffle``` - Shuffle the songs in queue
|
</p>
|
||||||
|
|
||||||
```!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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# **Usage:**
|
# **How to use it**
|
||||||
|
|
||||||
### **API Keys**
|
|
||||||
|
### **Requirements**
|
||||||
|
Installation of ``Python 3.10+`` and the dependencies in the requirements.txt file, creation of your own Bot in Discord and Spotify Keys. <br>
|
||||||
|
To install the dependencies type this command in the terminal, in the project root folder.
|
||||||
|
|
||||||
|
```
|
||||||
|
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)
|
* Your Discord Application - [Discord](https://discord.com/developers)
|
||||||
* You own Spotify Keys - [Spotify](https://developer.spotify.com/dashboard/applications)
|
* You own Spotify Keys - [Spotify](https://developer.spotify.com/dashboard/applications)
|
||||||
|
|
||||||
- This information must be stored in an .env file, explained further.
|
- This information must be stored in an .env file, explained further.
|
||||||
|
|
||||||
### **Requirements**
|
### **Installation of FFMPEG**<br>
|
||||||
- Installation of Python 3.8+ and the dependencies in the requirements.txt file.
|
|
||||||
```
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
- **Installation of FFMPEG**<br>
|
|
||||||
FFMPEG is a module that will be used to play music, you must have this configured in your machine
|
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>
|
*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.
|
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 +68,8 @@ BOT_PREFIX=Your_Wanted_Prefix_For_Vulkan
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### **Config File**
|
### **⚙️ Configs**
|
||||||
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.
|
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. <br>
|
||||||
|
|
||||||
|
|
||||||
### **Initialization**
|
### **Initialization**
|
||||||
@@ -87,25 +77,31 @@ The config file, located in ```./config``` folder doesn't require any change, bu
|
|||||||
- Run ```python main.py``` in console to start
|
- Run ```python main.py``` in console to start
|
||||||
|
|
||||||
|
|
||||||
## **Heroku**
|
### **Configuring Auto Disconnect**
|
||||||
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:
|
As a result of the [Issue 33](https://github.com/RafaelSolVargas/Vulkan/issues/33) now you can configure if the Bot will auto disconnect when being alone in the voice channel, the default configuration is to disconnect within 300 seconds if it finds out no one is currently listing to it.
|
||||||
|
To change that you must: <br>
|
||||||
- https://github.com/jonathanong/heroku-buildpack-ffmpeg-latest.git
|
- Change the property SHOULD_AUTO_DISCONNECT_WHEN_ALONE of the VConfigs class to False
|
||||||
|
> The path to the file is ./Config/Configs.py
|
||||||
- https://github.com/xrisk/heroku-opus.git
|
|
||||||
|
|
||||||
|
|
||||||
## Testing
|
<br>
|
||||||
|
<hr>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## **🚀 Heroku (Not free anymore)**
|
||||||
|
> *Heroku doesn't offer free host services anymore.* <br>
|
||||||
|
|
||||||
|
To deploy and run your Bot in Heroku 24/7, follow the instructions in the [Heroku Instructions](HEROKU.md) page.
|
||||||
|
|
||||||
|
## 🧪 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>
|
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>
|
`python run_tests.py`<br>
|
||||||
|
|
||||||
## 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).
|
## 📖 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
|
|
||||||
- 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.
|
|
||||||
|
|
||||||
|
## 🏗️ Contributing
|
||||||
## Acknowledgment
|
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.
|
||||||
- See the DingoLingo [project](https://github.com/Raptor123471/DingoLingo) from Raptor123471, it helped me a lot to build Vulkan.
|
|
||||||
|
|||||||
12
UI/Buttons/AbstractItem.py
Normal file
12
UI/Buttons/AbstractItem.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from discord.ui import Item, View
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractItem(ABC, Item):
|
||||||
|
@abstractmethod
|
||||||
|
def set_view(self, view: View):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_view(self) -> View:
|
||||||
|
pass
|
||||||
48
UI/Buttons/CallbackButton.py
Normal file
48
UI/Buttons/CallbackButton.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
from typing import Awaitable
|
||||||
|
from Config.Emojis import VEmojis
|
||||||
|
from discord import ButtonStyle, Interaction, Message, TextChannel
|
||||||
|
from discord.ui import Button, View
|
||||||
|
from Handlers.HandlerResponse import HandlerResponse
|
||||||
|
from Messages.MessagesCategory import MessagesCategory
|
||||||
|
from Messages.MessagesManager import MessagesManager
|
||||||
|
from Music.VulkanBot import VulkanBot
|
||||||
|
|
||||||
|
|
||||||
|
class CallbackButton(Button):
|
||||||
|
"""When clicked execute an callback passing the args and kwargs"""
|
||||||
|
|
||||||
|
def __init__(self, bot: VulkanBot, cb: Awaitable, emoji: VEmojis, textChannel: TextChannel, guildID: int, category: MessagesCategory, label=None, *args, **kwargs):
|
||||||
|
super().__init__(label=label, style=ButtonStyle.secondary, emoji=emoji)
|
||||||
|
self.__channel = textChannel
|
||||||
|
self.__guildID = guildID
|
||||||
|
self.__category = category
|
||||||
|
self.__messagesManager = MessagesManager()
|
||||||
|
self.__bot = bot
|
||||||
|
self.__args = args
|
||||||
|
self.__kwargs = kwargs
|
||||||
|
self.__callback = cb
|
||||||
|
self.__view: View = None
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
response: HandlerResponse = await self.__callback(*self.__args, **self.__kwargs)
|
||||||
|
|
||||||
|
message = None
|
||||||
|
if response and response.view is not None:
|
||||||
|
message: Message = await self.__channel.send(embed=response.embed, view=response.view)
|
||||||
|
response.view.set_message(message)
|
||||||
|
elif response.embed:
|
||||||
|
message: Message = await self.__channel.send(embed=response.embed)
|
||||||
|
|
||||||
|
# Clear the last sended message in this category and add the new one
|
||||||
|
if message:
|
||||||
|
await self.__messagesManager.addMessageAndClearPrevious(self.__guildID, self.__category, message, response.view)
|
||||||
|
|
||||||
|
def set_view(self, view: View):
|
||||||
|
self.__view = view
|
||||||
|
|
||||||
|
def get_view(self) -> View:
|
||||||
|
return self.__view
|
||||||
50
UI/Buttons/HandlerButton.py
Normal file
50
UI/Buttons/HandlerButton.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
from Config.Emojis import VEmojis
|
||||||
|
from discord import ButtonStyle, Interaction, Message, TextChannel
|
||||||
|
from discord.ui import Button, View
|
||||||
|
from Handlers.HandlerResponse import HandlerResponse
|
||||||
|
from Messages.MessagesCategory import MessagesCategory
|
||||||
|
from Music.VulkanBot import VulkanBot
|
||||||
|
from Handlers.AbstractHandler import AbstractHandler
|
||||||
|
from Messages.MessagesManager import MessagesManager
|
||||||
|
|
||||||
|
|
||||||
|
class HandlerButton(Button):
|
||||||
|
"""Button that will create and execute a Handler Object when clicked"""
|
||||||
|
|
||||||
|
def __init__(self, bot: VulkanBot, handler: type[AbstractHandler], emoji: VEmojis, textChannel: TextChannel, guildID: int, category: MessagesCategory, label=None, *args, **kwargs):
|
||||||
|
super().__init__(label=label, style=ButtonStyle.secondary, emoji=emoji)
|
||||||
|
self.__messagesManager = MessagesManager()
|
||||||
|
self.__category = category
|
||||||
|
self.__guildID = guildID
|
||||||
|
self.__channel = textChannel
|
||||||
|
self.__bot = bot
|
||||||
|
self.__args = args
|
||||||
|
self.__kwargs = kwargs
|
||||||
|
self.__handlerClass = handler
|
||||||
|
self.__view: View = None
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
# Create the handler object
|
||||||
|
handler = self.__handlerClass(interaction, self.__bot)
|
||||||
|
response: HandlerResponse = await handler.run(*self.__args, **self.__kwargs)
|
||||||
|
|
||||||
|
message = None
|
||||||
|
if response and response.view is not None:
|
||||||
|
message: Message = await self.__channel.send(embed=response.embed, view=response.view)
|
||||||
|
response.view.set_message(message)
|
||||||
|
elif response.embed:
|
||||||
|
message: Message = await self.__channel.send(embed=response.embed)
|
||||||
|
|
||||||
|
# Clear the last category sended message and add the new one
|
||||||
|
if message:
|
||||||
|
await self.__messagesManager.addMessageAndClearPrevious(self.__guildID, self.__category, message, response.view)
|
||||||
|
|
||||||
|
def set_view(self, view: View):
|
||||||
|
self.__view = view
|
||||||
|
|
||||||
|
def get_view(self) -> View:
|
||||||
|
return self.__view
|
||||||
89
UI/Buttons/PlaylistDropdown.py
Normal file
89
UI/Buttons/PlaylistDropdown.py
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import asyncio
|
||||||
|
from typing import List
|
||||||
|
from discord import Interaction, Message, TextChannel, SelectOption
|
||||||
|
from discord.ui import Select, View
|
||||||
|
from Handlers.HandlerResponse import HandlerResponse
|
||||||
|
from Messages.MessagesCategory import MessagesCategory
|
||||||
|
from Messages.MessagesManager import MessagesManager
|
||||||
|
from Music.VulkanBot import VulkanBot
|
||||||
|
from Handlers.AbstractHandler import AbstractHandler
|
||||||
|
from UI.Buttons.AbstractItem import AbstractItem
|
||||||
|
from UI.Views.AbstractView import AbstractView
|
||||||
|
from Music.Playlist import Playlist
|
||||||
|
|
||||||
|
|
||||||
|
class PlaylistDropdown(Select, AbstractItem):
|
||||||
|
"""Receives n elements to put in drop down and return the selected, pass the index value to a handler"""
|
||||||
|
|
||||||
|
def __init__(self, bot: VulkanBot, handler: type[AbstractHandler], playlist: Playlist, textChannel: TextChannel, guildID: int, category: MessagesCategory):
|
||||||
|
songs = list(playlist.getSongs())
|
||||||
|
|
||||||
|
values = [str(x) for x in range(1, len(songs) + 1)]
|
||||||
|
# Get the title of each of the 20 first songs, the pycord library doesn't accept more
|
||||||
|
songsNames: List[str] = []
|
||||||
|
songsLength = min(20, len(songs))
|
||||||
|
for x in range(songsLength):
|
||||||
|
songsNames.append(f'{x + 1} - {songs[x].title[:80]}')
|
||||||
|
|
||||||
|
selectOptions: List[SelectOption] = []
|
||||||
|
|
||||||
|
for x in range(len(songsNames)):
|
||||||
|
selectOptions.append(SelectOption(label=songsNames[x], value=values[x]))
|
||||||
|
|
||||||
|
super().__init__(placeholder="Select one music to play now, may be outdated",
|
||||||
|
min_values=1, max_values=1, options=selectOptions)
|
||||||
|
|
||||||
|
self.__playlist = playlist
|
||||||
|
self.__channel = textChannel
|
||||||
|
self.__guildID = guildID
|
||||||
|
self.__category = category
|
||||||
|
self.__handlerClass = handler
|
||||||
|
self.__messagesManager = MessagesManager()
|
||||||
|
self.__bot = bot
|
||||||
|
self.__view: AbstractView = None
|
||||||
|
|
||||||
|
async def callback(self, interaction: Interaction) -> None:
|
||||||
|
"""Callback to when the selection is selected"""
|
||||||
|
await interaction.response.defer()
|
||||||
|
|
||||||
|
# Execute the handler passing the value selected
|
||||||
|
handler = self.__handlerClass(interaction, self.__bot)
|
||||||
|
response: HandlerResponse = await handler.run(self.values[0])
|
||||||
|
|
||||||
|
message = None
|
||||||
|
if response and response.view is not None:
|
||||||
|
message: Message = await self.__channel.send(embed=response.embed, view=response.view)
|
||||||
|
elif response.embed:
|
||||||
|
message: Message = await self.__channel.send(embed=response.embed)
|
||||||
|
|
||||||
|
# Clear the last sended message in this category and add the new one
|
||||||
|
if message:
|
||||||
|
await self.__messagesManager.addMessageAndClearPrevious(self.__guildID, self.__category, message, response.view)
|
||||||
|
|
||||||
|
# Extreme ugly way to wait for the player process to actually retrieve the next song
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
await self.__update()
|
||||||
|
|
||||||
|
async def __update(self):
|
||||||
|
songs = list(self.__playlist.getSongs())
|
||||||
|
|
||||||
|
values = [str(x) for x in range(1, len(songs) + 1)]
|
||||||
|
# Get the title of each of the 20 first songs, library doesn't accept more
|
||||||
|
songsNames = [song.title[:80] for song in songs[:20]]
|
||||||
|
|
||||||
|
selectOptions: List[SelectOption] = []
|
||||||
|
|
||||||
|
for x in range(len(songsNames)):
|
||||||
|
selectOptions.append(SelectOption(label=songsNames[x], value=values[x]))
|
||||||
|
|
||||||
|
self.options = selectOptions
|
||||||
|
|
||||||
|
if self.__view is not None:
|
||||||
|
await self.__view.update()
|
||||||
|
|
||||||
|
def set_view(self, view: View):
|
||||||
|
self.__view = view
|
||||||
|
|
||||||
|
def get_view(self) -> View:
|
||||||
|
return self.__view
|
||||||
14
UI/Views/AbstractView.py
Normal file
14
UI/Views/AbstractView.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractView(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
async def update(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_message(self, message) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def stopView(self) -> None:
|
||||||
|
pass
|
||||||
56
UI/Views/BasicView.py
Normal file
56
UI/Views/BasicView.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
from UI.Views.AbstractView import AbstractView
|
||||||
|
from UI.Buttons.AbstractItem import AbstractItem
|
||||||
|
from Music.VulkanBot import VulkanBot
|
||||||
|
from Config.Emojis import VEmojis
|
||||||
|
from discord import Message
|
||||||
|
from discord.ui import View
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
emojis = VEmojis()
|
||||||
|
|
||||||
|
|
||||||
|
class BasicView(View, AbstractView):
|
||||||
|
"""
|
||||||
|
View class that inherits from the Discord View Class, managing a list of Buttons
|
||||||
|
and the message that holds this View.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, bot: VulkanBot, buttons: List[AbstractItem], timeout: float = 6000):
|
||||||
|
super().__init__(timeout=timeout)
|
||||||
|
self.__bot = bot
|
||||||
|
self.__message: Message = None
|
||||||
|
self.__working = True
|
||||||
|
|
||||||
|
for button in buttons:
|
||||||
|
# Set the buttons to have a instance of the view that contains them
|
||||||
|
button.set_view(self)
|
||||||
|
self.add_item(button)
|
||||||
|
|
||||||
|
def stopView(self):
|
||||||
|
self.__working = False
|
||||||
|
|
||||||
|
async def on_timeout(self) -> None:
|
||||||
|
# Disable all itens and, if has the message, edit it
|
||||||
|
try:
|
||||||
|
if not self.__working:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.disable_all_items()
|
||||||
|
if self.__message is not None and isinstance(self.__message, Message):
|
||||||
|
await self.__message.edit(f"{emojis.MUSIC} - The buttons in this message have been disabled due timeout", view=self)
|
||||||
|
except Exception as e:
|
||||||
|
print(f'[ERROR EDITING MESSAGE] -> {e}')
|
||||||
|
|
||||||
|
def set_message(self, message: Message) -> None:
|
||||||
|
self.__message = message
|
||||||
|
|
||||||
|
async def update(self):
|
||||||
|
"""Edit the message sending the view again"""
|
||||||
|
try:
|
||||||
|
if not self.__working:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.__message is not None:
|
||||||
|
await self.__message.edit(view=self)
|
||||||
|
except Exception as e:
|
||||||
|
print(f'[ERROR UPDATING MESSAGE] -> {e}')
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
from typing import List
|
from typing import List
|
||||||
from discord.ext.commands import Context
|
from discord.ext.commands import Context
|
||||||
from discord import Client, Message, Embed
|
from discord import Message, Embed
|
||||||
from Config.Singleton import Singleton
|
from Config.Singleton import Singleton
|
||||||
|
from Music.VulkanBot import VulkanBot
|
||||||
|
|
||||||
|
|
||||||
class Cleaner(Singleton):
|
class Cleaner(Singleton):
|
||||||
def __init__(self, bot: Client = None) -> None:
|
def __init__(self, bot: VulkanBot = None) -> None:
|
||||||
if not super().created:
|
if not super().created:
|
||||||
self.__bot = bot
|
self.__bot = bot
|
||||||
self.__clean_str = 'Uploader:'
|
self.__clean_str = 'Uploader:'
|
||||||
|
|
||||||
def set_bot(self, bot: Client) -> None:
|
def set_bot(self, bot: VulkanBot) -> None:
|
||||||
self.__bot = bot
|
self.__bot = bot
|
||||||
|
|
||||||
async def clean_messages(self, ctx: Context, quant: int) -> None:
|
async def clean_messages(self, ctx: Context, quant: int) -> None:
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import re
|
import re
|
||||||
import asyncio
|
import asyncio
|
||||||
from Config.Configs import Configs
|
from Config.Configs import VConfigs
|
||||||
from functools import wraps, partial
|
from functools import wraps, partial
|
||||||
config = Configs()
|
config = VConfigs()
|
||||||
|
|
||||||
|
|
||||||
class Utils:
|
class Utils:
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
from abc import ABC, abstractmethod
|
|
||||||
from Handlers.HandlerResponse import HandlerResponse
|
|
||||||
from discord.ext.commands import Context
|
|
||||||
from discord import Client, Message
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractView(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
|
|
||||||
|
|
||||||
@property
|
|
||||||
def response(self) -> HandlerResponse:
|
|
||||||
return self.__response
|
|
||||||
|
|
||||||
@property
|
|
||||||
def bot(self) -> Client:
|
|
||||||
return self.__bot
|
|
||||||
|
|
||||||
@property
|
|
||||||
def message(self) -> Message:
|
|
||||||
return self.__message
|
|
||||||
|
|
||||||
@property
|
|
||||||
def context(self) -> Context:
|
|
||||||
return self.__context
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def run(self) -> None:
|
|
||||||
pass
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
from Views.AbstractView import AbstractView
|
|
||||||
from Handlers.HandlerResponse import HandlerResponse
|
|
||||||
|
|
||||||
|
|
||||||
class EmbedView(AbstractView):
|
|
||||||
def __init__(self, response: HandlerResponse) -> None:
|
|
||||||
super().__init__(response)
|
|
||||||
|
|
||||||
async def run(self) -> None:
|
|
||||||
if self.response.embed:
|
|
||||||
await self.context.send(embed=self.response.embed)
|
|
||||||
@@ -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('❌')
|
|
||||||
42
main.py
42
main.py
@@ -1,38 +1,8 @@
|
|||||||
from discord import Intents, Client
|
from Music.VulkanInitializer import VulkanInitializer
|
||||||
from os import listdir
|
from Config.Folder import Folder
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
vulkan = VulkanInitializer()
|
folder = Folder()
|
||||||
vulkan.run()
|
initializer = VulkanInitializer(willListen=True)
|
||||||
|
vulkanBot = initializer.getBot()
|
||||||
|
vulkanBot.startBot()
|
||||||
|
|||||||
BIN
requirements.txt
BIN
requirements.txt
Binary file not shown.
Reference in New Issue
Block a user