mirror of
https://github.com/RafaelSolVargas/Vulkan.git
synced 2025-10-29 16:57:23 +00:00
Merge pull request #19 from RafaelSolVargas/addingDezzer
Adding dezzer option to play musics
This commit is contained in:
commit
d87a0234ba
@ -75,10 +75,10 @@ class SearchMessages(Singleton):
|
|||||||
config = Configs()
|
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 = 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.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.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.SPOTIFY_NOT_FOUND = 'Spotify 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.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):
|
class SpotifyMessages(Singleton):
|
||||||
@ -86,3 +86,10 @@ class SpotifyMessages(Singleton):
|
|||||||
if not super().created:
|
if not super().created:
|
||||||
self.INVALID_SPOTIFY_URL = 'Invalid Spotify URL, verify your link.'
|
self.INVALID_SPOTIFY_URL = 'Invalid Spotify URL, verify your link.'
|
||||||
self.GENERIC_TITLE = 'URL could not be processed'
|
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'
|
||||||
|
|||||||
@ -49,6 +49,11 @@ class SpotifyError(VulkanError):
|
|||||||
super().__init__(message, title, *args)
|
super().__init__(message, title, *args)
|
||||||
|
|
||||||
|
|
||||||
|
class DeezerError(VulkanError):
|
||||||
|
def __init__(self, message='', title='', *args: object) -> None:
|
||||||
|
super().__init__(message, title, *args)
|
||||||
|
|
||||||
|
|
||||||
class UnknownError(VulkanError):
|
class UnknownError(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)
|
||||||
|
|||||||
72
Music/DeezerSearcher.py
Normal file
72
Music/DeezerSearcher.py
Normal file
@ -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
|
||||||
@ -65,6 +65,7 @@ class Downloader():
|
|||||||
with YoutubeDL(options) as ydl:
|
with YoutubeDL(options) as ydl:
|
||||||
try:
|
try:
|
||||||
extracted_info = ydl.extract_info(url, download=False)
|
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):
|
if self.__failed_to_extract(extracted_info):
|
||||||
extracted_info = self.__get_forced_extracted_info(url)
|
extracted_info = self.__get_forced_extracted_info(url)
|
||||||
|
|
||||||
|
|||||||
@ -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.Downloader import Downloader
|
||||||
from Music.Types import Provider
|
from Music.Types import Provider
|
||||||
from Music.Spotify import SpotifySearch
|
from Music.Spotify import SpotifySearch
|
||||||
|
from Music.DeezerSearcher import DeezerSearcher
|
||||||
from Utils.Utils import Utils
|
from Utils.Utils import Utils
|
||||||
from Utils.UrlAnalyzer import URLAnalyzer
|
from Utils.UrlAnalyzer import URLAnalyzer
|
||||||
from Config.Messages import SearchMessages
|
from Config.Messages import SearchMessages
|
||||||
@ -9,7 +10,8 @@ from Config.Messages import SearchMessages
|
|||||||
|
|
||||||
class Searcher():
|
class Searcher():
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.__Spotify = SpotifySearch()
|
self.__spotify = SpotifySearch()
|
||||||
|
self.__deezer = DeezerSearcher()
|
||||||
self.__messages = SearchMessages()
|
self.__messages = SearchMessages()
|
||||||
self.__down = Downloader()
|
self.__down = Downloader()
|
||||||
|
|
||||||
@ -24,20 +26,35 @@ class Searcher():
|
|||||||
musics = await self.__down.extract_info(track)
|
musics = await self.__down.extract_info(track)
|
||||||
return musics
|
return musics
|
||||||
except:
|
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:
|
elif provider == Provider.Spotify:
|
||||||
try:
|
try:
|
||||||
musics = self.__Spotify.search(track)
|
musics = self.__spotify.search(track)
|
||||||
if musics == None or len(musics) == 0:
|
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
|
return musics
|
||||||
except SpotifyError as error:
|
except SpotifyError as error:
|
||||||
raise error # Redirect already processed error
|
raise error # Redirect already processed error
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'[Spotify Error] -> {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:
|
elif provider == Provider.Name:
|
||||||
return [track]
|
return [track]
|
||||||
@ -52,7 +69,7 @@ class Searcher():
|
|||||||
if 'start_radio' or 'index' in trackAnalyzer.queryParams.keys():
|
if 'start_radio' or 'index' in trackAnalyzer.queryParams.keys():
|
||||||
return trackAnalyzer.getCleanedUrl()
|
return trackAnalyzer.getCleanedUrl()
|
||||||
|
|
||||||
def __identify_source(self, track) -> Provider:
|
def __identify_source(self, track: str) -> Provider:
|
||||||
if not Utils.is_url(track):
|
if not Utils.is_url(track):
|
||||||
return Provider.Name
|
return Provider.Name
|
||||||
|
|
||||||
@ -62,4 +79,7 @@ class Searcher():
|
|||||||
if "https://open.spotify.com" in track:
|
if "https://open.spotify.com" in track:
|
||||||
return Provider.Spotify
|
return Provider.Spotify
|
||||||
|
|
||||||
|
if "https://www.deezer.com" in track:
|
||||||
|
return Provider.Deezer
|
||||||
|
|
||||||
return Provider.Unknown
|
return Provider.Unknown
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
class Provider(Enum):
|
class Provider(str, Enum):
|
||||||
Spotify = 'Spotify'
|
Spotify = 'Spotify'
|
||||||
|
Deezer = 'Deezer'
|
||||||
YouTube = 'YouTube'
|
YouTube = 'YouTube'
|
||||||
Name = 'Track Name'
|
Name = 'Track Name'
|
||||||
Unknown = 'Unknown'
|
Unknown = 'Unknown'
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
# **Vulkan**
|
# **Vulkan**
|
||||||
|
|
||||||
A Music Discord bot, written in Python, that supports Youtube and Spotify sources for playing. 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.
|
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**
|
# **Music**
|
||||||
- Play musics from Youtube and Spotify Playlists
|
- Play musics from Youtube, Spotify and Deezer links (Albums, Artists, Playlists and Tracks)
|
||||||
- Control loop of one or all musics
|
- Control loop of one or all musics
|
||||||
- Allow moving and removing musics in the queue
|
- Allow moving and removing musics in the queue
|
||||||
- Play musics in queue randomly
|
- Play musics in queue randomly
|
||||||
- Store played songs and allow bidirectional flow
|
- Store played songs and allow bidirectional flow
|
||||||
|
|
||||||
### Commands
|
### Commands
|
||||||
```!play [title, spotify_url, youtube_url]``` - Start playing song
|
```!play [title, spotify_url, youtube_url, deezer_url]``` - Start playing song
|
||||||
|
|
||||||
```!resume``` - Resume the song player
|
```!resume``` - Resume the song player
|
||||||
|
|
||||||
|
|||||||
@ -19,3 +19,10 @@ class TestsConstants(Singleton):
|
|||||||
self.SPOTIFY_ALBUM_URL = 'https://open.spotify.com/album/71O60S5gIJSIAhdnrDIh3N'
|
self.SPOTIFY_ALBUM_URL = 'https://open.spotify.com/album/71O60S5gIJSIAhdnrDIh3N'
|
||||||
self.SPOTIFY_WRONG1_URL = 'https://open.spotify.com/wrongUrl'
|
self.SPOTIFY_WRONG1_URL = 'https://open.spotify.com/wrongUrl'
|
||||||
self.SPOTIFY_WRONG2_URL = 'https://open.spotify.com/track/WrongID'
|
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'
|
||||||
|
|||||||
66
Tests/VDeezerTests.py
Normal file
66
Tests/VDeezerTests.py
Normal file
@ -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
|
||||||
@ -58,15 +58,25 @@ class VulkanDownloaderTest(VulkanTesterBase):
|
|||||||
|
|
||||||
def test_YoutubeMixPlaylist(self) -> None:
|
def test_YoutubeMixPlaylist(self) -> None:
|
||||||
# Search the link to determine names
|
# Search the link to determine names
|
||||||
music = self._runner.run_coroutine(
|
musics = self._runner.run_coroutine(
|
||||||
self._searcher.search(self._constants.YT_MIX_URL))
|
self._searcher.search(self._constants.YT_MIX_URL))
|
||||||
|
|
||||||
# Musics from Mix should download only the first music
|
# Musics from Mix should download only the first music
|
||||||
if len(music) == 1:
|
if len(musics) != 1:
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
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):
|
def test_musicTitle(self):
|
||||||
playlist = Playlist()
|
playlist = Playlist()
|
||||||
song = Song(self._constants.MUSIC_TITLE_STRING, playlist, '')
|
song = Song(self._constants.MUSIC_TITLE_STRING, playlist, '')
|
||||||
@ -81,11 +91,30 @@ class VulkanDownloaderTest(VulkanTesterBase):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def test_YoutubePersonalPlaylist(self) -> None:
|
def test_YoutubePersonalPlaylist(self) -> None:
|
||||||
musics = self._runner.run_coroutine(
|
musicsList = self._runner.run_coroutine(
|
||||||
self._searcher.search(self._constants.YT_PERSONAL_PLAYLIST_URL))
|
self._searcher.search(self._constants.YT_PERSONAL_PLAYLIST_URL))
|
||||||
|
|
||||||
if len(musics) > 0:
|
if len(musicsList) == 0:
|
||||||
print(musics)
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
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
|
||||||
|
|||||||
BIN
requirements.txt
BIN
requirements.txt
Binary file not shown.
@ -1,8 +1,11 @@
|
|||||||
from Tests.VDownloaderTests import VulkanDownloaderTest
|
from Tests.VDownloaderTests import VulkanDownloaderTest
|
||||||
from Tests.VSpotifyTests import VulkanSpotifyTest
|
from Tests.VSpotifyTests import VulkanSpotifyTest
|
||||||
|
from Tests.VDeezerTests import VulkanDeezerTest
|
||||||
|
|
||||||
|
|
||||||
tester = VulkanDownloaderTest()
|
tester = VulkanDownloaderTest()
|
||||||
# tester.run()
|
tester.run()
|
||||||
tester = VulkanSpotifyTest()
|
tester = VulkanSpotifyTest()
|
||||||
tester.run()
|
tester.run()
|
||||||
|
tester = VulkanDeezerTest()
|
||||||
|
tester.run()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user