diff --git a/Config/Messages.py b/Config/Messages.py index afe26b0..dc09f71 100644 --- a/Config/Messages.py +++ b/Config/Messages.py @@ -75,10 +75,10 @@ class SearchMessages(Singleton): 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 = 'URL could not be processed' - self.YOUTUBE_ERROR = 'Youtube could not process any songs with this input, verify your link or try again later.' - self.INVALID_SPOTIFY_URL = 'Invalid Spotify URL, verify your link.' + self.SPOTIFY_NOT_FOUND = 'Spotify could not process any songs with this input, verify your link or try again later.' + self.YOUTUBE_NOT_FOUND = 'Youtube could not process any songs with this input, verify your link or try again later.' + self.DEEZER_NOT_FOUND = 'Deezer could not process any songs with this input, verify your link or try again later.' class SpotifyMessages(Singleton): @@ -86,3 +86,10 @@ class SpotifyMessages(Singleton): if not super().created: self.INVALID_SPOTIFY_URL = 'Invalid Spotify URL, verify your link.' self.GENERIC_TITLE = 'URL could not be processed' + + +class DeezerMessages(Singleton): + def __init__(self) -> None: + if not super().created: + self.INVALID_DEEZER_URL = 'Invalid Deezer URL, verify your link.' + self.GENERIC_TITLE = 'URL could not be processed' diff --git a/Exceptions/Exceptions.py b/Exceptions/Exceptions.py index 6c29478..dee5718 100644 --- a/Exceptions/Exceptions.py +++ b/Exceptions/Exceptions.py @@ -49,6 +49,11 @@ class SpotifyError(VulkanError): super().__init__(message, title, *args) +class DeezerError(VulkanError): + def __init__(self, message='', title='', *args: object) -> None: + super().__init__(message, title, *args) + + class UnknownError(VulkanError): def __init__(self, message='', title='', *args: object) -> None: super().__init__(message, title, *args) diff --git a/Music/DeezerSearcher.py b/Music/DeezerSearcher.py new file mode 100644 index 0000000..19048ef --- /dev/null +++ b/Music/DeezerSearcher.py @@ -0,0 +1,72 @@ +import deezer +from Exceptions.Exceptions import DeezerError +from Config.Messages import DeezerMessages + + +class DeezerSearcher: + def __init__(self) -> None: + self.__client = deezer.Client() + self.__messages = DeezerMessages() + self.__acceptedTypes = ['track', 'artist', 'playlist', 'album'] + + def search(self, url: str) -> None: + if not self.__verifyValidUrl(url): + raise DeezerError(self.__messages.INVALID_DEEZER_URL, self.__messages.GENERIC_TITLE) + + urlType = url.split('/')[4].split('?')[0] + code = int(url.split('/')[5].split('?')[0]) + + try: + musics = [] + if urlType == 'album': + musics = self.__get_album(code) + elif urlType == 'playlist': + musics = self.__get_playlist(code) + elif urlType == 'track': + musics = self.__get_track(code) + elif urlType == 'artist': + musics = self.__get_artist(code) + + return musics + except Exception as e: + print(f'[DEEZER ERROR] -> {e}') + raise DeezerError(self.__messages.INVALID_DEEZER_URL, self.__messages.GENERIC_TITLE) + + def __get_album(self, code: int) -> list: + album = self.__client.get_album(code) + + return [track.title for track in album.tracks] + + def __get_track(self, code: int) -> list: + track = self.__client.get_track(code) + + return [track.title] + + def __get_playlist(self, code: int) -> list: + playlist = self.__client.get_playlist(code) + + return [track.title for track in playlist.tracks] + + def __get_artist(self, code: int) -> list: + artist = self.__client.get_artist(code) + + topMusics = artist.get_top() + + return [track.title for track in topMusics] + + def __verifyValidUrl(self, url: str) -> bool: + try: + urlType = url.split('/')[4].split('?')[0] + code = url.split('/')[5].split('?')[0] + + code = int(code) + + if urlType == '' or code == '': + return False + + if urlType not in self.__acceptedTypes: + return False + + return True + except: + return False diff --git a/Music/Downloader.py b/Music/Downloader.py index 2c295f0..d5dcdbf 100644 --- a/Music/Downloader.py +++ b/Music/Downloader.py @@ -65,6 +65,7 @@ class Downloader(): with YoutubeDL(options) as ydl: try: extracted_info = ydl.extract_info(url, download=False) + # Some links doesn't extract unless extract_flat key is passed as False in options if self.__failed_to_extract(extracted_info): extracted_info = self.__get_forced_extracted_info(url) diff --git a/Music/Searcher.py b/Music/Searcher.py index 30624a9..4610588 100644 --- a/Music/Searcher.py +++ b/Music/Searcher.py @@ -1,7 +1,8 @@ -from Exceptions.Exceptions import InvalidInput, SpotifyError, YoutubeError +from Exceptions.Exceptions import DeezerError, InvalidInput, SpotifyError, YoutubeError from Music.Downloader import Downloader from Music.Types import Provider from Music.Spotify import SpotifySearch +from Music.DeezerSearcher import DeezerSearcher from Utils.Utils import Utils from Utils.UrlAnalyzer import URLAnalyzer from Config.Messages import SearchMessages @@ -9,7 +10,8 @@ from Config.Messages import SearchMessages class Searcher(): def __init__(self) -> None: - self.__Spotify = SpotifySearch() + self.__spotify = SpotifySearch() + self.__deezer = DeezerSearcher() self.__messages = SearchMessages() self.__down = Downloader() @@ -24,20 +26,35 @@ class Searcher(): musics = await self.__down.extract_info(track) return musics except: - raise YoutubeError(self.__messages.YOUTUBE_ERROR, self.__messages.GENERIC_TITLE) + raise YoutubeError(self.__messages.YOUTUBE_NOT_FOUND, self.__messages.GENERIC_TITLE) elif provider == Provider.Spotify: try: - musics = self.__Spotify.search(track) + musics = self.__spotify.search(track) if musics == None or len(musics) == 0: - raise SpotifyError(self.__messages.SPOTIFY_ERROR, self.__messages.GENERIC_TITLE) + raise SpotifyError(self.__messages.SPOTIFY_NOT_FOUND, + self.__messages.GENERIC_TITLE) return musics except SpotifyError as error: raise error # Redirect already processed error except Exception as e: print(f'[Spotify Error] -> {e}') - raise SpotifyError(self.__messages.SPOTIFY_ERROR, self.__messages.GENERIC_TITLE) + raise SpotifyError(self.__messages.SPOTIFY_NOT_FOUND, self.__messages.GENERIC_TITLE) + + elif provider == Provider.Deezer: + try: + musics = self.__deezer.search(track) + if musics == None or len(musics) == 0: + raise DeezerError(self.__messages.DEEZER_NOT_FOUND, + self.__messages.GENERIC_TITLE) + + return musics + except DeezerError as error: + raise error # Redirect already processed error + except Exception as e: + print(f'[Deezer Error] -> {e}') + raise DeezerError(self.__messages.DEEZER_NOT_FOUND, self.__messages.GENERIC_TITLE) elif provider == Provider.Name: return [track] @@ -52,7 +69,7 @@ class Searcher(): if 'start_radio' or 'index' in trackAnalyzer.queryParams.keys(): return trackAnalyzer.getCleanedUrl() - def __identify_source(self, track) -> Provider: + def __identify_source(self, track: str) -> Provider: if not Utils.is_url(track): return Provider.Name @@ -62,4 +79,7 @@ class Searcher(): if "https://open.spotify.com" in track: return Provider.Spotify + if "https://www.deezer.com" in track: + return Provider.Deezer + return Provider.Unknown diff --git a/Music/Types.py b/Music/Types.py index 7ec21de..61d09bc 100644 --- a/Music/Types.py +++ b/Music/Types.py @@ -1,8 +1,9 @@ from enum import Enum -class Provider(Enum): +class Provider(str, Enum): Spotify = 'Spotify' + Deezer = 'Deezer' YouTube = 'YouTube' Name = 'Track Name' Unknown = 'Unknown' diff --git a/Tests/TestsHelper.py b/Tests/TestsHelper.py index a44326f..fb49c20 100644 --- a/Tests/TestsHelper.py +++ b/Tests/TestsHelper.py @@ -19,3 +19,10 @@ class TestsConstants(Singleton): self.SPOTIFY_ALBUM_URL = 'https://open.spotify.com/album/71O60S5gIJSIAhdnrDIh3N' self.SPOTIFY_WRONG1_URL = 'https://open.spotify.com/wrongUrl' self.SPOTIFY_WRONG2_URL = 'https://open.spotify.com/track/WrongID' + + self.DEEZER_TRACK_URL = 'https://www.deezer.com/br/track/33560861' + self.DEEZER_ARTIST_URL = 'https://www.deezer.com/br/artist/180' + self.DEEZER_PLAYLIST_URL = 'https://www.deezer.com/br/playlist/1001939451' + self.DEEZER_ALBUM_URL = 'https://www.deezer.com/en/album/236107012' + self.DEEZER_WRONG1_URL = 'xxxhttps://www.deezer.com/br/album/5' + self.DEEZER_WRONG2_URL = 'https://www.deezer.com/en/album/23610701252' diff --git a/Tests/VDeezerTests.py b/Tests/VDeezerTests.py new file mode 100644 index 0000000..063e790 --- /dev/null +++ b/Tests/VDeezerTests.py @@ -0,0 +1,66 @@ +from Tests.TestBase import VulkanTesterBase +from Exceptions.Exceptions import DeezerError + + +class VulkanDeezerTest(VulkanTesterBase): + def __init__(self) -> None: + super().__init__() + + def test_deezerTrack(self) -> bool: + musics = self._runner.run_coroutine( + self._searcher.search(self._constants.DEEZER_TRACK_URL)) + + if len(musics) > 0: + return True + else: + return False + + def test_deezerPlaylist(self) -> bool: + musics = self._runner.run_coroutine( + self._searcher.search(self._constants.DEEZER_PLAYLIST_URL)) + + if len(musics) > 0: + return True + else: + return False + + def test_deezerArtist(self) -> bool: + musics = self._runner.run_coroutine( + self._searcher.search(self._constants.DEEZER_ARTIST_URL)) + + if len(musics) > 0: + return True + else: + return False + + def test_deezerAlbum(self) -> bool: + musics = self._runner.run_coroutine( + self._searcher.search(self._constants.DEEZER_ALBUM_URL)) + + if len(musics) > 0: + return True + else: + return False + + def test_deezerWrongUrlShouldThrowException(self) -> bool: + try: + musics = self._runner.run_coroutine( + self._searcher.search(self._constants.DEEZER_WRONG1_URL)) + + except DeezerError as e: + print(f'Deezer Error -> {e.message}') + return True + except Exception as e: + print(e) + return False + + def test_deezerWrongUrlTwoShouldThrowException(self) -> bool: + try: + musics = self._runner.run_coroutine( + self._searcher.search(self._constants.DEEZER_WRONG2_URL)) + + except DeezerError as e: + print(f'Deezer Error -> {e.message}') + return True + except Exception as e: + return False diff --git a/Tests/VDownloaderTests.py b/Tests/VDownloaderTests.py index e64b2f1..65daf9c 100644 --- a/Tests/VDownloaderTests.py +++ b/Tests/VDownloaderTests.py @@ -58,15 +58,25 @@ class VulkanDownloaderTest(VulkanTesterBase): def test_YoutubeMixPlaylist(self) -> None: # Search the link to determine names - music = self._runner.run_coroutine( + musics = self._runner.run_coroutine( self._searcher.search(self._constants.YT_MIX_URL)) # Musics from Mix should download only the first music - if len(music) == 1: - return True - else: + if len(musics) != 1: return False + playlist = Playlist() + song = Song(musics[0], playlist, '') + playlist.add_song(song) + + self._runner.run_coroutine(self._downloader.download_song(song)) + + if song.problematic: + return False + else: + print(song.title) + return True + def test_musicTitle(self): playlist = Playlist() song = Song(self._constants.MUSIC_TITLE_STRING, playlist, '') @@ -81,11 +91,30 @@ class VulkanDownloaderTest(VulkanTesterBase): return True def test_YoutubePersonalPlaylist(self) -> None: - musics = self._runner.run_coroutine( + musicsList = self._runner.run_coroutine( self._searcher.search(self._constants.YT_PERSONAL_PLAYLIST_URL)) - if len(musics) > 0: - print(musics) - return True - else: + if len(musicsList) == 0: return False + + # Create and store songs in list + playlist = Playlist() + songsList: List[Song] = [] + for info in musicsList: + song = Song(identifier=info, playlist=playlist, requester='') + playlist.add_song(song) + songsList.append(song) + + # Create a list of coroutines without waiting for them + tasks: List[Task] = [] + for song in songsList: + tasks.append(self._downloader.download_song(song)) + + # Send for runner to execute them concurrently + self._runner.run_coroutines_list(tasks) + + for song in songsList: + if not song.problematic and song.title == None: + return False + + return True diff --git a/requirements.txt b/requirements.txt index 2fe4355..9519446 100644 Binary files a/requirements.txt and b/requirements.txt differ diff --git a/run_tests.py b/run_tests.py index 03417ba..2eadf89 100644 --- a/run_tests.py +++ b/run_tests.py @@ -1,8 +1,11 @@ from Tests.VDownloaderTests import VulkanDownloaderTest from Tests.VSpotifyTests import VulkanSpotifyTest +from Tests.VDeezerTests import VulkanDeezerTest tester = VulkanDownloaderTest() -# tester.run() +tester.run() tester = VulkanSpotifyTest() tester.run() +tester = VulkanDeezerTest() +tester.run()