diff --git a/demo/.DS_Store b/demo/.DS_Store deleted file mode 100644 index 5008ddf..0000000 Binary files a/demo/.DS_Store and /dev/null differ diff --git a/rip/cli.py b/rip/cli.py index 52e8e7f..aedfaaa 100644 --- a/rip/cli.py +++ b/rip/cli.py @@ -464,11 +464,18 @@ class ConfigCommand(Command): import getpass import hashlib - self._config.file["qobuz"]["email"] = self.ask("Qobuz email:") - self._config.file["qobuz"]["password"] = hashlib.md5( - getpass.getpass("Qobuz password (won't show on screen): ").encode() - ).hexdigest() - self._config.save() + self._config.file["qobuz"]["use_auth_token"] = self.confirm("Use Qobuz auth token to authenticate?", default=False) + + if self._config.file["qobuz"]["use_auth_token"]: + self._config.file["qobuz"]["email_or_userid"] = self.ask("Qobuz user id:") + self._config.file["qobuz"]["password_or_token"] = getpass.getpass("Qobuz auth token (won't show on screen): ") + self._config.save() + else: + self._config.file["qobuz"]["email_or_userid"] = self.ask("Qobuz email:") + self._config.file["qobuz"]["password_or_token"] = hashlib.md5( + getpass.getpass("Qobuz password (won't show on screen): ").encode() + ).hexdigest() + self._config.save() if self.option("music-app"): self._conf_music_app() diff --git a/rip/config.py b/rip/config.py index 4bc5fc5..88adaa2 100644 --- a/rip/config.py +++ b/rip/config.py @@ -172,8 +172,9 @@ class Config: def qobuz_creds(self): """Return a QobuzClient compatible dict of credentials.""" return { - "email": self.file["qobuz"]["email"], - "pwd": self.file["qobuz"]["password"], + "use_auth_token": self.file["qobuz"]["use_auth_token"], + "email_or_userid": self.file["qobuz"]["email_or_userid"], + "password_or_token": self.file["qobuz"]["password_or_token"], "app_id": self.file["qobuz"]["app_id"], "secrets": self.file["qobuz"]["secrets"], } diff --git a/rip/config.toml b/rip/config.toml index 7549e7b..c37d715 100644 --- a/rip/config.toml +++ b/rip/config.toml @@ -20,9 +20,12 @@ quality = 3 # This will download booklet pdfs that are included with some albums download_booklets = true -email = "" -# This is an md5 hash of the plaintext password -password = "" +# Authenticate to Qobuz using auth token? Value can be true/false only +use_auth_token = false +# Enter your userid if the above use_auth_token is set to true, else enter your email +email_or_userid = "" +# Enter your auth token if the above use_auth_token is set to true, else enter the md5 hash of your plaintext password +password_or_token = "" # Do not change app_id = "" # Do not change @@ -157,7 +160,6 @@ restrict_characters = false # Setting this to false may cause downloads to fail on some systems truncate = true - # Last.fm playlists are downloaded by searching for the titles of the tracks [lastfm] # The source on which to search for the tracks. diff --git a/rip/core.py b/rip/core.py index b9f18ad..f616e48 100644 --- a/rip/core.py +++ b/rip/core.py @@ -878,21 +878,37 @@ class RipCore(list): :type source: str """ if source == "qobuz": - secho("Enter Qobuz email:", fg="green") - self.config.file[source]["email"] = input() - secho( - "Enter Qobuz password (will not show on screen):", - fg="green", - ) - self.config.file[source]["password"] = md5( - getpass(prompt="").encode("utf-8") - ).hexdigest() + secho("Use Qobuz auth token to authenticate? (yes/no)", fg="green") + use_auth_token = re.match("(?i)^y", input()) is not None + + self.config.file[source]["use_auth_token"] = use_auth_token + + if use_auth_token: + secho("Enter Qobuz user id:", fg="green") + self.config.file[source]["email_or_userid"] = input() - self.config.save() - secho( - f'Credentials saved to config file at "{self.config._path}"', - fg="green", - ) + secho("Enter Qobuz token (will not show on screen):", fg="green") + self.config.file[source]["password_or_token"] = getpass(prompt="") + + self.config.save() + secho( + f'Credentials saved to config file at "{self.config._path}"', + fg="green", + ) + else: + secho("Enter Qobuz email:", fg="green") + self.config.file[source]["email_or_userid"] = input() + + secho("Enter Qobuz password (will not show on screen):", fg="green") + self.config.file[source]["password_or_token"] = md5( + getpass(prompt="").encode("utf-8") + ).hexdigest() + + self.config.save() + secho( + f'Credentials saved to config file at "{self.config._path}"', + fg="green", + ) elif source == "deezer": secho( "If you're not sure how to find the ARL cookie, see the instructions at ", diff --git a/rip/utils.py b/rip/utils.py index dcf1a5f..f072ce1 100644 --- a/rip/utils.py +++ b/rip/utils.py @@ -6,6 +6,9 @@ from typing import Tuple from streamrip.constants import AGENT from streamrip.utils import gen_threadsafe_session +interpreter_artist_id_regex = re.compile( + r"https?://www\.qobuz\.com/\w\w-\w\w/interpreter/[-\w]+/(?P<artistId>[0-9]+)" +) interpreter_artist_regex = re.compile(r"getSimilarArtist\(\s*'(\w+)'") @@ -13,9 +16,14 @@ def extract_interpreter_url(url: str) -> str: """Extract artist ID from a Qobuz interpreter url. :param url: Urls of the form "https://www.qobuz.com/us-en/interpreter/{artist}/download-streaming-albums" + or "https://www.qobuz.com/us-en/interpreter/the-last-shadow-puppets/{artistId}}" :type url: str :rtype: str """ + url_match = interpreter_artist_id_regex.search(url) + if url_match: + return url_match.group("artistId") + session = gen_threadsafe_session({"User-Agent": AGENT}) r = session.get(url) match = interpreter_artist_regex.search(r.text) diff --git a/streamrip/clients.py b/streamrip/clients.py index 3b62f11..dcc1ed4 100644 --- a/streamrip/clients.py +++ b/streamrip/clients.py @@ -117,9 +117,10 @@ class QobuzClient(Client): :param kwargs: app_id: str, secrets: list, return_secrets: bool """ secho(f"Logging into {self.source}", fg="green") - email: str = kwargs["email"] - pwd: str = kwargs["pwd"] - if not email or not pwd: + use_auth_token: bool = kwargs["use_auth_token"] + email_or_userid: str = kwargs["email_or_userid"] + password_or_token: str = kwargs["password_or_token"] + if not email_or_userid or not password_or_token: raise MissingCredentials if self.logged_in: @@ -138,7 +139,7 @@ class QobuzClient(Client): ) self._validate_secrets() - self._api_login(email, pwd) + self._api_login(use_auth_token, email_or_userid, password_or_token) logger.debug("Logged into Qobuz") logger.debug("Qobuz client is ready to use") @@ -342,19 +343,28 @@ class QobuzClient(Client): return self._gen_pages(epoint, params) - def _api_login(self, email: str, pwd: str): + def _api_login(self, use_auth_token: bool, email_or_userid: str, password_or_token: str): """Log into the api to get the user authentication token. - :param email: - :type email: str - :param pwd: - :type pwd: str + :param use_auth_token: + :type use_auth_token: bool + :param email_or_userid: + :type email_or_userid: str + :param password_or_token: + :type password_or_token: str """ - params = { - "email": email, - "password": pwd, - "app_id": self.app_id, - } + if use_auth_token: + params = { + "user_id": email_or_userid, + "user_auth_token": password_or_token, + "app_id": self.app_id, + } + else: + params = { + "email": email_or_userid, + "password": password_or_token, + "app_id": self.app_id, + } epoint = "user/login" resp, status_code = self._api_request(epoint, params)