2023-03-17 17:16:06 +00:00
|
|
|
from typing import Optional
|
|
|
|
import requests
|
|
|
|
import logging
|
|
|
|
|
|
|
|
LOGGER = logging.getLogger("this shouldn't be used")
|
|
|
|
|
|
|
|
from ..utils import shared
|
2023-01-23 13:53:35 +00:00
|
|
|
|
2023-03-10 09:13:35 +00:00
|
|
|
from ..objects import (
|
2023-01-23 13:53:35 +00:00
|
|
|
Song,
|
|
|
|
Source,
|
|
|
|
Album,
|
|
|
|
Artist,
|
|
|
|
Lyrics,
|
|
|
|
Target,
|
2023-03-13 13:39:46 +00:00
|
|
|
MusicObject,
|
2023-03-20 13:40:32 +00:00
|
|
|
Options,
|
|
|
|
SourcePages
|
2023-01-23 13:53:35 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class Page:
|
|
|
|
"""
|
|
|
|
This is an abstract class, laying out the
|
|
|
|
functionality for every other class fetching something
|
|
|
|
"""
|
|
|
|
|
2023-03-17 17:16:06 +00:00
|
|
|
API_SESSION: requests.Session = requests.Session()
|
|
|
|
API_SESSION.proxies = shared.proxies
|
|
|
|
TIMEOUT = 5
|
|
|
|
TRIES = 5
|
2023-03-20 13:40:32 +00:00
|
|
|
|
|
|
|
SOURCE_TYPE: SourcePages
|
2023-03-17 17:16:06 +00:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_request(cls, url: str, accepted_response_codes: set = set((200,)), trie: int = 0) -> Optional[
|
|
|
|
requests.Request]:
|
|
|
|
try:
|
|
|
|
r = cls.API_SESSION.get(url, timeout=cls.TIMEOUT)
|
|
|
|
except requests.exceptions.Timeout:
|
|
|
|
return None
|
|
|
|
|
|
|
|
if r.status_code in accepted_response_codes:
|
|
|
|
return r
|
|
|
|
|
|
|
|
LOGGER.warning(f"{cls.__name__} responded wit {r.status_code} at {url}. ({trie}-{cls.TRIES})")
|
|
|
|
LOGGER.debug(r.content)
|
|
|
|
|
|
|
|
if trie <= cls.TRIES:
|
|
|
|
LOGGER.warning("to many tries. Aborting.")
|
|
|
|
|
|
|
|
return cls.get_request(url, accepted_response_codes, trie + 1)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def post_request(cls, url: str, json: dict, accepted_response_codes: set = set((200,)), trie: int = 0) -> Optional[
|
|
|
|
requests.Request]:
|
|
|
|
try:
|
|
|
|
r = cls.API_SESSION.post(url, json=json, timeout=cls.TIMEOUT)
|
|
|
|
except requests.exceptions.Timeout:
|
|
|
|
return None
|
|
|
|
|
|
|
|
if r.status_code in accepted_response_codes:
|
|
|
|
return r
|
|
|
|
|
|
|
|
LOGGER.warning(f"{cls.__name__} responded wit {r.status_code} at {url}. ({trie}-{cls.TRIES})")
|
|
|
|
LOGGER.debug(r.content)
|
|
|
|
|
|
|
|
if trie <= cls.TRIES:
|
|
|
|
LOGGER.warning("to many tries. Aborting.")
|
|
|
|
|
|
|
|
return cls.post_request(url, accepted_response_codes, trie + 1)
|
|
|
|
|
2023-01-23 23:16:10 +00:00
|
|
|
class Query:
|
|
|
|
def __init__(self, query: str):
|
|
|
|
self.query = query
|
|
|
|
self.is_raw = False
|
|
|
|
|
|
|
|
self.artist = None
|
|
|
|
self.album = None
|
|
|
|
self.song = None
|
|
|
|
|
|
|
|
self.parse_query(query=query)
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
if self.is_raw:
|
|
|
|
return self.query
|
|
|
|
return f"{self.artist}; {self.album}; {self.song}"
|
|
|
|
|
|
|
|
def parse_query(self, query: str):
|
|
|
|
if not '#' in query:
|
|
|
|
self.is_raw = True
|
|
|
|
return
|
|
|
|
|
|
|
|
query = query.strip()
|
|
|
|
parameters = query.split('#')
|
|
|
|
parameters.remove('')
|
|
|
|
|
|
|
|
for parameter in parameters:
|
|
|
|
splitted = parameter.split(" ")
|
|
|
|
type_ = splitted[0]
|
|
|
|
input_ = " ".join(splitted[1:]).strip()
|
|
|
|
|
|
|
|
if type_ == "a":
|
|
|
|
self.artist = input_
|
|
|
|
continue
|
|
|
|
if type_ == "r":
|
|
|
|
self.album = input_
|
|
|
|
continue
|
|
|
|
if type_ == "t":
|
2023-01-24 09:51:41 +00:00
|
|
|
self.song = input_
|
2023-01-23 23:16:10 +00:00
|
|
|
continue
|
|
|
|
|
2023-01-24 09:51:41 +00:00
|
|
|
def get_str(self, string):
|
|
|
|
if string is None:
|
|
|
|
return ""
|
|
|
|
return string
|
|
|
|
|
|
|
|
artist_str = property(fget=lambda self: self.get_str(self.artist))
|
|
|
|
album_str = property(fget=lambda self: self.get_str(self.album))
|
|
|
|
song_str = property(fget=lambda self: self.get_str(self.song))
|
|
|
|
|
2023-01-23 13:53:35 +00:00
|
|
|
@classmethod
|
2023-03-17 17:16:06 +00:00
|
|
|
def search_by_query(cls, query: str) -> Options:
|
2023-01-23 13:53:35 +00:00
|
|
|
"""
|
|
|
|
# The Query
|
|
|
|
You can define a new parameter with "#",
|
|
|
|
the letter behind it defines the *type* of parameter, followed by a space
|
|
|
|
"#a Psychonaut 4 #r Tired, Numb and #t Drop by Drop"
|
|
|
|
if no # is in the query it gets treated as "unspecified query"
|
|
|
|
|
|
|
|
# Functionality
|
|
|
|
Returns the best matches from this page for the query, passed in.
|
|
|
|
|
|
|
|
:param query:
|
|
|
|
:return possible_music_objects:
|
|
|
|
"""
|
|
|
|
|
2023-03-13 13:39:46 +00:00
|
|
|
return Options()
|
2023-01-23 13:53:35 +00:00
|
|
|
|
|
|
|
@classmethod
|
2023-02-06 14:06:38 +00:00
|
|
|
def fetch_details(cls, music_object: MusicObject, flat: bool = False) -> MusicObject:
|
2023-01-23 13:53:35 +00:00
|
|
|
"""
|
|
|
|
when a music object with laccing data is passed in, it returns
|
|
|
|
the SAME object **(no copy)** with more detailed data.
|
|
|
|
If you for example put in an album, it fetches the tracklist
|
|
|
|
|
|
|
|
:param music_object:
|
2023-02-06 14:06:38 +00:00
|
|
|
:param flat:
|
2023-01-23 13:53:35 +00:00
|
|
|
if it is true it fetches only the most important information (only one level)
|
|
|
|
if an Artist is passed in, it fetches only the discography of the artist, and not the
|
|
|
|
tracklist of every album of the artist.
|
2023-01-23 14:52:50 +00:00
|
|
|
:return detailed_music_object: IT MODIFIES THE INPUT OBJ
|
2023-01-23 13:53:35 +00:00
|
|
|
"""
|
|
|
|
|
2023-01-23 14:52:50 +00:00
|
|
|
if type(music_object) == Song:
|
2023-03-14 12:36:05 +00:00
|
|
|
song = cls.fetch_song_details(music_object, flat=flat)
|
|
|
|
song.compile()
|
|
|
|
return song
|
2023-03-17 17:16:06 +00:00
|
|
|
|
2023-01-23 14:52:50 +00:00
|
|
|
if type(music_object) == Album:
|
2023-03-14 12:36:05 +00:00
|
|
|
album = cls.fetch_album_details(music_object, flat=flat)
|
|
|
|
album.compile()
|
|
|
|
return album
|
2023-01-23 14:52:50 +00:00
|
|
|
|
|
|
|
if type(music_object) == Artist:
|
2023-03-14 12:36:05 +00:00
|
|
|
artist = cls.fetch_artist_details(music_object, flat=flat)
|
|
|
|
artist.compile()
|
|
|
|
return artist
|
2023-01-23 14:52:50 +00:00
|
|
|
|
|
|
|
raise NotImplementedError(f"MusicObject {type(music_object)} has not been implemented yet")
|
|
|
|
|
2023-03-20 13:40:32 +00:00
|
|
|
@classmethod
|
|
|
|
def fetch_song_from_source(cls, source: Source, flat: bool = False) -> Song:
|
|
|
|
return Song()
|
|
|
|
|
2023-02-06 14:06:38 +00:00
|
|
|
@classmethod
|
|
|
|
def fetch_song_details(cls, song: Song, flat: bool = False) -> Song:
|
2023-01-23 14:52:50 +00:00
|
|
|
"""
|
|
|
|
for a general description check cls.fetch_details
|
|
|
|
|
|
|
|
:param song: song without much data
|
2023-02-06 14:06:38 +00:00
|
|
|
:param flat:
|
2023-01-23 14:52:50 +00:00
|
|
|
when True it only fetches the artist and the album, and the attributes of those,
|
|
|
|
who can be gotten with one api request
|
|
|
|
when False it fetches everything including, but not limited to:
|
|
|
|
- Lyrics
|
|
|
|
- Album + Tracklist (for tracksort)
|
|
|
|
|
|
|
|
:return detailed_song: it modifies the input song
|
|
|
|
"""
|
2023-03-20 13:40:32 +00:00
|
|
|
|
|
|
|
source: Source
|
|
|
|
for source in song.source_collection.get_sources_from_page(cls.SOURCE_TYPE):
|
|
|
|
new_song = cls.fetch_song_from_source(source, flat)
|
|
|
|
song.merge(new_song)
|
2023-01-23 14:52:50 +00:00
|
|
|
|
2023-02-06 14:06:38 +00:00
|
|
|
return song
|
2023-01-23 14:52:50 +00:00
|
|
|
|
2023-03-20 13:40:32 +00:00
|
|
|
@classmethod
|
|
|
|
def fetch_album_from_source(cls, source: Source, flat: bool = False) -> Album:
|
|
|
|
return Album()
|
|
|
|
|
2023-02-06 14:06:38 +00:00
|
|
|
@classmethod
|
|
|
|
def fetch_album_details(cls, album: Album, flat: bool = False) -> Album:
|
2023-01-23 14:52:50 +00:00
|
|
|
"""
|
|
|
|
for a general description check cls.fetch_details
|
|
|
|
|
|
|
|
:param album: album without much data
|
2023-02-06 14:06:38 +00:00
|
|
|
:param flat:
|
2023-01-23 14:52:50 +00:00
|
|
|
when True it only fetches the artist and the tracklist, and the attributes of those,
|
|
|
|
which can be gotten with one api request
|
|
|
|
when False it fetches everything including, but not limited to:
|
|
|
|
- Lyrics of every song
|
|
|
|
- Artist, Album, Tracklist
|
|
|
|
- every attribute of those
|
|
|
|
|
|
|
|
:return detailed_artist: it modifies the input artist
|
|
|
|
"""
|
|
|
|
|
2023-03-20 13:40:32 +00:00
|
|
|
source: Source
|
|
|
|
for source in album.source_collection.get_sources_from_page(cls.SOURCE_TYPE):
|
|
|
|
new_album: Album = cls.fetch_album_from_source(source, flat)
|
|
|
|
album.merge(new_album)
|
|
|
|
|
2023-02-06 14:06:38 +00:00
|
|
|
return album
|
2023-01-23 14:52:50 +00:00
|
|
|
|
2023-03-20 13:40:32 +00:00
|
|
|
@classmethod
|
|
|
|
def fetch_artist_from_source(cls, source: Source, flat: bool = False) -> Artist:
|
|
|
|
return Artist()
|
|
|
|
|
2023-02-06 14:06:38 +00:00
|
|
|
@classmethod
|
|
|
|
def fetch_artist_details(cls, artist: Artist, flat: bool = False) -> Artist:
|
2023-01-23 14:52:50 +00:00
|
|
|
"""
|
|
|
|
for a general description check cls.fetch_details
|
|
|
|
|
|
|
|
:param artist: artist without much data
|
2023-02-06 14:06:38 +00:00
|
|
|
:param flat:
|
2023-01-23 14:52:50 +00:00
|
|
|
when True it only fetches the discographie, meaning every album, but not every tracklist
|
|
|
|
when False it fetches everything including, but not limited to:
|
|
|
|
- the whole discography
|
|
|
|
- the tracklist of every album in the discography
|
|
|
|
|
|
|
|
:return detailed_artist: it modifies the input artist
|
|
|
|
"""
|
2023-03-20 13:40:32 +00:00
|
|
|
|
|
|
|
source: Source
|
|
|
|
for source in artist.source_collection.get_sources_from_page(cls.SOURCE_TYPE):
|
|
|
|
new_artist: Artist = cls.fetch_artist_from_source(source, flat)
|
|
|
|
artist.merge(new_artist)
|
2023-01-23 14:52:50 +00:00
|
|
|
|
2023-02-06 14:06:38 +00:00
|
|
|
return artist
|