Upgrading PlayController and Spotify Connection

This commit is contained in:
Rafael Vargas 2022-03-26 17:42:49 -04:00
parent f30513f710
commit 4c66c64041
15 changed files with 213 additions and 163 deletions

View File

@ -1,13 +1,20 @@
import asyncio
from Exceptions.Exceptions import DownloadingError, Error
from discord.ext.commands import Context
from discord import Client
from Controllers.AbstractController import AbstractController
from Exceptions.Exceptions import ImpossibleMove, UnknownError
from Controllers.ControllerResponse import ControllerResponse
from Music.Downloader import Downloader
from Music.Searcher import Searcher
from Music.Song import Song
class PlayController(AbstractController):
def __init__(self, ctx: Context, bot: Client) -> None:
super().__init__(ctx, bot)
self.__searcher = Searcher()
self.__down = Downloader()
async def run(self, args: str) -> ControllerResponse:
track = " ".join(args)
@ -25,7 +32,47 @@ class PlayController(AbstractController):
embed = self.embeds.UNKNOWN_ERROR()
return ControllerResponse(self.ctx, embed, error)
await self.player.play(self.ctx, track, requester)
try:
musics = await self.__searcher.search(track)
for music in musics:
song = Song(music, self.player.playlist, requester)
self.player.playlist.add_song(song)
quant = len(musics)
songs_preload = self.player.playlist.songs_to_preload
await self.__down.preload(songs_preload)
if quant == 1:
pos = len(self.player.playlist)
song = self.__down.finish_one_song(song)
if song.problematic:
embed = self.embeds.SONG_PROBLEMATIC()
error = DownloadingError()
response = ControllerResponse(self.ctx, embed, error)
elif not self.player.playing:
embed = self.embeds.SONG_ADDED(song.title)
response = ControllerResponse(self.ctx, embed)
else:
embed = self.embeds.SONG_ADDED_TWO(song.info, pos)
response = ControllerResponse(self.ctx, embed)
else:
embed = self.embeds.SONGS_ADDED(quant)
response = ControllerResponse(self.ctx, embed)
asyncio.create_task(self.player.play(self.ctx))
return response
except Exception as err:
if isinstance(err, Error):
print(f'DEVELOPER NOTE -> PlayController Error: {err.message}')
error = err
embed = self.embeds.CUSTOM_ERROR(error)
else:
error = UnknownError()
embed = self.embeds.UNKNOWN_ERROR()
return ControllerResponse(self.ctx, embed, error)
def __user_connected(self) -> bool:
if self.ctx.author.voice:

View File

@ -1,3 +1,4 @@
import asyncio
from discord.ext.commands import Context
from discord import Client
from Controllers.AbstractController import AbstractController
@ -16,7 +17,7 @@ class ShuffleController(AbstractController):
self.player.playlist.shuffle()
songs = self.player.playlist.songs_to_preload
await self.__down.preload(songs)
asyncio.create_task(self.__down.preload(songs))
embed = self.embeds.SONGS_SHUFFLED()
return ControllerResponse(self.ctx, embed)
except Exception as e:

View File

@ -34,6 +34,16 @@ class BadCommandUsage(Error):
super().__init__(message, title, *args)
class DownloadingError(Error):
def __init__(self, message='', title='', *args: object) -> None:
super().__init__(message, title, *args)
class SpotifyError(Error):
def __init__(self, message='', title='', *args: object) -> None:
super().__init__(message, title, *args)
class UnknownError(Error):
def __init__(self, message='', title='', *args: object) -> None:
super().__init__(message, title, *args)

View File

@ -1,5 +1,7 @@
import asyncio
from typing import List
from numpy import extract
from Config.Config import Configs
from yt_dlp import YoutubeDL
from concurrent.futures import ThreadPoolExecutor
@ -25,7 +27,7 @@ class Downloader():
'default_search': 'auto',
'playliststart': 0,
'extract_flat': False,
'playlistend': config.MAX_PLAYLIST_LENGTH,
'playlistend': config.MAX_PLAYLIST_FORCED_LENGTH,
}
__BASE_URL = 'https://www.youtube.com/watch?v={}'
@ -127,7 +129,10 @@ class Downloader():
extracted_info = ydl.extract_info(search, download=False)
if self.__failed_to_extract(extracted_info):
self.__get_forced_extracted_info(extracted_info)
extracted_info = self.__get_forced_extracted_info(title)
if extracted_info is None:
return {}
if self.__is_multiple_musics(extracted_info):
return extracted_info['entries'][0]

View File

@ -36,71 +36,7 @@ class Player(commands.Cog):
def playlist(self) -> Playlist:
return self.__playlist
async def play(self, ctx: Context, track: str, requester: str) -> str:
try:
links, provider = self.__searcher.search(track)
if provider == Provider.Unknown or links == None:
embed = Embed(
title=self.__config.ERROR_TITLE,
description=self.__config.INVALID_INPUT,
colours=self.__config.COLOURS['blue'])
await ctx.send(embed=embed)
return None
if provider == Provider.YouTube:
links = await self.__down.extract_info(links[0])
if len(links) == 0:
embed = Embed(
title=self.__config.ERROR_TITLE,
description="This video is unavailable",
colours=self.__config.COLOURS['blue'])
await ctx.send(embed=embed)
return None
songs_quant = 0
for info in links:
song = self.__playlist.add_song(info, requester)
songs_quant += 1
songs_preload = self.__playlist.songs_to_preload
await self.__down.preload(songs_preload)
except Exception as e:
print(f'DEVELOPER NOTE -> Error while Downloading in Player: {e}')
embed = Embed(
title=self.__config.ERROR_TITLE,
description=self.__config.DOWNLOADING_ERROR,
colours=self.__config.COLOURS['blue'])
await ctx.send(embed=embed)
return
if songs_quant == 1:
song = self.__down.finish_one_song(song)
pos = len(self.__playlist)
if song.problematic:
embed = Embed(
title=self.__config.ERROR_TITLE,
description=self.__config.DOWNLOADING_ERROR,
colours=self.__config.COLOURS['blue'])
await ctx.send(embed=embed)
return None
elif not self.__playing:
embed = Embed(
title=self.__config.SONG_PLAYER,
description=self.__config.SONG_ADDED.format(song.title),
colour=self.__config.COLOURS['blue'])
await ctx.send(embed=embed)
else:
embed = self.__format_embed(song.info, self.__config.SONG_ADDED_TWO, pos)
await ctx.send(embed=embed)
else:
embed = Embed(
title=self.__config.SONG_PLAYER,
description=self.__config.SONGS_ADDED.format(songs_quant),
colour=self.__config.COLOURS['blue'])
await ctx.send(embed=embed)
async def play(self, ctx: Context) -> str:
if not self.__playing:
first_song = self.__playlist.next_song()
await self.__play_music(ctx, first_song)

View File

@ -92,8 +92,7 @@ class Playlist(IPlaylist):
self.__current = last_song
return self.__current # return the song
def add_song(self, identifier: str, requester: str) -> Song:
song = Song(identifier=identifier, playlist=self, requester=requester)
def add_song(self, song: Song) -> Song:
self.__queue.append(song)
return song

View File

@ -1,40 +1,44 @@
from Exceptions.Exceptions import InvalidInput, SpotifyError
from Music.Downloader import Downloader
from Music.Types import Provider
from Music.Spotify import SpotifySearch
from Utils.Utils import is_url
from Utils.Utils import Utils
from Config.Messages import SearchMessages
class Searcher():
def __init__(self) -> None:
self.__Spotify = SpotifySearch()
self.__messages = SearchMessages()
self.__down = Downloader()
def search(self, music: str) -> list:
provider = self.__identify_source(music)
async def search(self, track: str) -> list:
provider = self.__identify_source(track)
if provider == Provider.Unknown:
raise InvalidInput(self.__messages.UNKNOWN_INPUT, self.__messages.UNKNOWN_INPUT_TITLE)
if provider == Provider.YouTube:
return [music], Provider.YouTube
elif provider == Provider.YouTube:
musics = await self.__down.extract_info(track)
return musics
elif provider == Provider.Spotify:
if self.__Spotify.connected == True:
musics = self.__Spotify.search(music)
return musics, Provider.Name
else:
print('DEVELOPER NOTE -> Spotify Not Connected')
return [], Provider.Unknown
try:
musics = self.__Spotify.search(track)
return musics
except:
raise SpotifyError(self.__messages.SPOTIFY_ERROR, self.__messages.GENERIC_TITLE)
elif provider == Provider.Name:
return [music], Provider.Name
return [track]
elif provider == Provider.Unknown:
return None, Provider.Unknown
def __identify_source(self, music) -> Provider:
if not is_url(music):
def __identify_source(self, track) -> Provider:
if not Utils.is_url(track):
return Provider.Name
if "https://www.youtu" in music or "https://youtu.be" in music or "https://music.youtube" in music:
if "https://www.youtu" in track or "https://youtu.be" in track or "https://music.youtube" in track:
return Provider.YouTube
if "https://open.spotify.com" in music:
if "https://open.spotify.com" in track:
return Provider.Spotify
return Provider.Unknown

View File

@ -17,17 +17,15 @@ class Song(ISong):
self.__required_keys = ['url']
for key in self.__required_keys:
if key in info:
if key in info.keys():
self.__info[key] = info[key]
else:
print(f'DEVELOPER NOTE -> {key} not found in info of music: {self.identifier}')
self.destroy()
for key in self.__usefull_keys:
if key in info:
if key in info.keys():
self.__info[key] = info[key]
else:
print(f'DEVELOPER NOTE -> {key} not found in info of music: {self.identifier}')
@property
def source(self) -> str:

View File

@ -1,4 +1,4 @@
import spotipy
from spotipy import Spotify
from spotipy.oauth2 import SpotifyClientCredentials
from Config.Config import Configs
@ -9,86 +9,67 @@ class SpotifySearch():
self.__connected = False
self.__connect()
@property
def connected(self):
return self.__connected
def __connect(self) -> bool:
def __connect(self) -> None:
try:
# Initialize the connection with Spotify API
self.__api = spotipy.Spotify(auth_manager=SpotifyClientCredentials(
client_id=self.__config.SPOTIFY_ID, client_secret=self.__config.SPOTIFY_SECRET))
auth = SpotifyClientCredentials(self.__config.SPOTIFY_ID, self.__config.SPOTIFY_SECRET)
self.__api = Spotify(auth_manager=auth)
self.__connected = True
return True
except Exception as e:
print(f'DEVELOPER NOTE -> Spotify Connection {e}')
return False
print(f'DEVELOPER NOTE -> Spotify Connection Error {e}')
def search(self, music=str) -> list:
def search(self, music: str) -> list:
type = music.split('/')[3].split('?')[0]
code = music.split('/')[4].split('?')[0]
if type == 'album':
musics = self.__get_album(code)
elif type == 'playlist':
musics = self.__get_playlist(code)
elif type == 'track':
musics = self.__get_track(code)
elif type == 'artist':
musics = self.__get_artist(code)
else:
return None
musics = []
if self.__connected:
if type == 'album':
musics = self.__get_album(code)
elif type == 'playlist':
musics = self.__get_playlist(code)
elif type == 'track':
musics = self.__get_track(code)
elif type == 'artist':
musics = self.__get_artist(code)
return musics
def __get_album(self, code=str) -> list:
if self.__connected == True:
try:
results = self.__api.album_tracks(code)
musics = results['items']
def __get_album(self, code: str) -> list:
results = self.__api.album_tracks(code)
musics = results['items']
while results['next']: # Get the next pages
results = self.__api.next(results)
musics.extend(results['items'])
while results['next']: # Get the next pages
results = self.__api.next(results)
musics.extend(results['items'])
musicsTitle = []
musicsTitle = []
for music in musics:
try:
title = self.__extract_title(music)
musicsTitle.append(title)
except:
pass
return musicsTitle
except Exception as e:
raise e
for music in musics:
title = self.__extract_title(music)
musicsTitle.append(title)
def __get_playlist(self, code=str) -> list:
try:
results = self.__api.playlist_items(code)
itens = results['items']
return musicsTitle
while results['next']: # Load the next pages
results = self.__api.next(results)
itens.extend(results['items'])
def __get_playlist(self, code: str) -> list:
results = self.__api.playlist_items(code)
itens = results['items']
musics = []
for item in itens:
musics.append(item['track'])
while results['next']: # Load the next pages
results = self.__api.next(results)
itens.extend(results['items'])
titles = []
for music in musics:
try:
title = self.__extract_title(music)
titles.append(title)
except Exception as e:
raise e
musics = []
for item in itens:
musics.append(item['track'])
return titles
titles = []
for music in musics:
title = self.__extract_title(music)
titles.append(title)
except Exception as e:
raise e
return titles
def __get_track(self, code=str) -> list:
def __get_track(self, code: str) -> list:
results = self.__api.track(code)
name = results['name']
artists = ''
@ -97,7 +78,7 @@ class SpotifySearch():
return [f'{name} {artists}']
def __get_artist(self, code=str) -> list:
def __get_artist(self, code: str) -> list:
results = self.__api.artist_top_tracks(code, country='BR')
musics_titles = []

12
Utils/Sender.py Normal file
View File

@ -0,0 +1,12 @@
from discord.ext.commands import Context
from discord import Embed
class Sender:
@classmethod
async def send_embed(cls, ctx: Context, embed: Embed) -> None:
pass
@classmethod
async def send_message(cls, ctx: Context, message: Embed) -> None:
pass

View File

@ -5,6 +5,33 @@ from functools import wraps, partial
config = Configs()
class Utils:
@classmethod
def format_time(cls, duration) -> str:
if not duration:
return "00:00"
hours = duration // 60 // 60
minutes = duration // 60 % 60
seconds = duration % 60
return "{}{}{:02d}:{:02d}".format(
hours if hours else "",
":" if hours else "",
minutes,
seconds)
@classmethod
def is_url(cls, string) -> bool:
regex = re.compile(
"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+")
if re.search(regex, string):
return True
else:
return False
def is_connected(ctx):
try:
voice_channel = ctx.guild.voice_client.channel

View File

@ -1,3 +1,4 @@
from Exceptions.Exceptions import Error
from discord import Embed
from Config.Config import Configs
from Config.Colors import Colors
@ -23,32 +24,36 @@ class Embeds:
)
return embed
def SONG_ADDED_TWO(self, info: dict, pos: int) -> Embed:
embed = self.SONG_INFO(info, self.__config.SONG_ADDED_TWO, pos)
return embed
def INVALID_INPUT(self) -> Embed:
embed = Embed(
title=self.__config.ERROR_TITLE,
description=self.__config.INVALID_INPUT,
colours=self.__colors.BLUE)
colour=self.__colors.BLACK)
return embed
def UNAVAILABLE_VIDEO(self) -> Embed:
embed = Embed(
title=self.__config.ERROR_TITLE,
description=self.__config.VIDEO_UNAVAILABLE,
colours=self.__colors.BLUE)
colour=self.__colors.BLACK)
return embed
def DOWNLOADING_ERROR(self) -> Embed:
embed = Embed(
title=self.__config.ERROR_TITLE,
description=self.__config.DOWNLOADING_ERROR,
colours=self.__colors.BLUE)
colour=self.__colors.BLACK)
return embed
def SONG_ADDED(self, title: str) -> Embed:
embed = Embed(
title=self.__config.SONG_PLAYER,
description=self.__config.SONG_ADDED.format(title),
colours=self.__colors.BLUE)
colour=self.__colors.BLUE)
return embed
def SONGS_ADDED(self, quant: int) -> Embed:
@ -62,7 +67,7 @@ class Embeds:
embedvc = Embed(
title=title,
description=f"[{info['title']}]({info['original_url']})",
color=self.__colors.BLUE
colour=self.__colors.BLUE
)
embedvc.add_field(name=self.__config.SONGINFO_UPLOADER,
@ -115,6 +120,14 @@ class Embeds:
)
return embed
def CUSTOM_ERROR(self, error: Error) -> Embed:
embed = Embed(
title=error.title,
description=error.message,
colour=self.__colors.BLACK
)
return embed
def WRONG_LENGTH_INPUT(self) -> Embed:
embed = Embed(
title=self.__config.BAD_COMMAND_TITLE,
@ -201,6 +214,13 @@ class Embeds:
)
return embed
def SONG_PROBLEMATIC(self) -> Embed:
embed = Embed(
title=self.__config.ERROR_TITLE,
description=self.__config.DOWNLOADING_ERROR,
colour=self.__colors.BLACK)
return embed
def NO_CHANNEL(self) -> Embed:
embed = Embed(
title=self.__config.IMPOSSIBLE_MOVE,

View File

@ -69,3 +69,13 @@ class Messages(Singleton):
self.BAD_COMMAND = f'❌ Bad usage of this command, type {configs.BOT_PREFIX}help "command" to understand the command better'
self.INVITE_URL = 'https://discordapp.com/oauth2/authorize?client_id={}&scope=bot>'
self.VIDEO_UNAVAILABLE = '❌ Sorry. This video is unavailable for download.'
class SearchMessages(Singleton):
def __init__(self) -> None:
if not super().created:
config = Configs()
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.SPOTIFY_ERROR = 'Spotify could not process any songs with this input, verify your link or try again later.'
self.GENERIC_TITLE = 'Input could not be processed'

View File

@ -17,6 +17,7 @@ class Configs(Singleton):
self.STARTUP_COMPLETE_MESSAGE = 'Vulkan is now operating.'
self.MAX_PLAYLIST_LENGTH = 50
self.MAX_PLAYLIST_FORCED_LENGTH = 5
self.MAX_PRELOAD_SONGS = 10
self.MAX_SONGS_HISTORY = 15

View File

@ -1,4 +1,3 @@
from distutils.command.config import config
from discord import Intents, Client
from os import listdir
from Config.Config import Configs