Use asynchronous requests for QobuzClient startup/login

This commit is contained in:
nathom 2021-08-12 10:50:11 -07:00
parent e9f40923ba
commit 6e0731ed0b
4 changed files with 54 additions and 36 deletions

View file

@ -46,6 +46,7 @@ class DownloadCommand(Command):
# Use a thread so that it doesn't slow down startup
update_check = threading.Thread(target=is_outdated, daemon=True)
update_check.start()
config = Config()
path, codec, quality, no_db = clean_options(
@ -83,16 +84,12 @@ class DownloadCommand(Command):
elif not urls and path is None:
self.line("<error>Must pass arguments. See </><cmd>rip url -h</cmd>.")
try:
update_check.join()
if outdated:
self.line(
"<info>A new version of streamrip is available! Run</info> "
"<cmd>pip3 install streamrip --upgrade to update</cmd>"
)
except RuntimeError as e:
logger.debug("Update check error: %s", e)
pass
update_check.join()
if outdated:
self.line(
"<info>A new version of streamrip is available! Run</info> "
"<cmd>pip3 install streamrip --upgrade to update</cmd>"
)
return 0

View file

@ -8,6 +8,7 @@ import re
from getpass import getpass
from hashlib import md5
from string import Formatter
import threading
from typing import Dict, Generator, List, Optional, Tuple, Type, Union
import requests
@ -385,9 +386,16 @@ class RipCore(list):
creds = self.config.creds(client.source)
except MissingCredentials:
logger.debug("Credentials are missing. Prompting..")
get_tokens = threading.Thread(
target=client._get_app_id_and_secrets, daemon=True
)
get_tokens.start()
self.prompt_creds(client.source)
creds = self.config.creds(client.source)
get_tokens.join()
if (
client.source == "qobuz"
and not creds.get("secrets")

View file

@ -8,7 +8,7 @@ import logging
import re
import time
from abc import ABC, abstractmethod
from pprint import pformat
import concurrent.futures
from typing import Any, Dict, Generator, Optional, Sequence, Tuple, Union
import deezer
@ -129,23 +129,17 @@ class QobuzClient(Client):
logger.debug("Already logged in")
return
if not (kwargs.get("app_id") or kwargs.get("secrets")):
secho("Fetching tokens — this may take a few seconds.", fg="magenta")
logger.info("Fetching tokens from Qobuz")
spoofer = Spoofer()
kwargs["app_id"] = spoofer.get_app_id()
kwargs["secrets"] = spoofer.get_secrets()
self.app_id = str(kwargs["app_id"]) # Ensure it is a string
self.secrets = kwargs["secrets"]
self.session = gen_threadsafe_session(
headers={"User-Agent": AGENT, "X-App-Id": self.app_id}
)
if not kwargs.get("app_id") or not kwargs.get("secrets"):
self._get_app_id_and_secrets() # can be async
else:
self.app_id, self.secrets = str(kwargs["app_id"]), kwargs["secrets"]
self.session = gen_threadsafe_session(
headers={"User-Agent": AGENT, "X-App-Id": self.app_id}
)
self._validate_secrets()
self._api_login(email, pwd)
logger.debug("Logged into Qobuz")
self._validate_secrets()
logger.debug("Qobuz client is ready to use")
self.logged_in = True
@ -218,6 +212,18 @@ class QobuzClient(Client):
# ---------- Private Methods ---------------
def _get_app_id_and_secrets(self):
if not hasattr(self, "app_id") or not hasattr(self, "secrets"):
spoofer = Spoofer()
self.app_id, self.secrets = str(spoofer.get_app_id()), spoofer.get_secrets()
if not hasattr(self, "sec"):
if not hasattr(self, "session"):
self.session = gen_threadsafe_session(
headers={"User-Agent": AGENT, "X-App-Id": self.app_id}
)
self._validate_secrets()
def _gen_pages(self, epoint: str, params: dict) -> Generator:
"""When there are multiple pages of results, this yields them.
@ -249,11 +255,17 @@ class QobuzClient(Client):
def _validate_secrets(self):
"""Check if the secrets are usable."""
for secret in self.secrets:
if self._test_secret(secret):
self.sec = secret
logger.debug("Working secret and app_id: %s - %s", secret, self.app_id)
break
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = [
executor.submit(self._test_secret, secret) for secret in self.secrets
]
for future in concurrent.futures.as_completed(futures):
result = future.result()
if result is not None:
self.sec = result
break
if not hasattr(self, "sec"):
raise InvalidAppSecretError(f"Invalid secrets: {self.secrets}")
@ -386,7 +398,7 @@ class QobuzClient(Client):
else:
raise InvalidAppSecretError("Cannot find app secret")
quality = int(get_quality(quality, self.source))
quality = int(get_quality(quality, self.source)) # type: ignore
r_sig = f"trackgetFileUrlformat_id{quality}intentstreamtrack_id{track_id}{unix_ts}{secret}"
logger.debug("Raw request signature: %s", r_sig)
r_sig_hashed = hashlib.md5(r_sig.encode("utf-8")).hexdigest()
@ -423,7 +435,7 @@ class QobuzClient(Client):
logger.error("Problem getting JSON. Status code: %s", r.status_code)
raise
def _test_secret(self, secret: str) -> bool:
def _test_secret(self, secret: str) -> Optional[str]:
"""Test the authenticity of a secret.
:param secret:
@ -432,10 +444,10 @@ class QobuzClient(Client):
"""
try:
self._api_get_file_url("19512574", sec=secret)
return True
return secret
except InvalidAppSecretError as error:
logger.debug("Test for %s secret didn't work: %s", secret, error)
return False
return None
class DeezerClient(Client):

View file

@ -6,6 +6,7 @@ Credits to Dash for this tool.
import base64
import re
from collections import OrderedDict
from typing import List
import requests
@ -50,7 +51,7 @@ class Spoofer:
raise Exception("Could not find app id.")
def get_secrets(self):
def get_secrets(self) -> List[str]:
"""Get secrets."""
seed_matches = re.finditer(self.seed_timezone_regex, self.bundle)
secrets = OrderedDict()
@ -81,6 +82,6 @@ class Spoofer:
"".join(secrets[secret_pair])[:-44]
).decode("utf-8")
vals = list(secrets.values())
vals: List[str] = list(secrets.values())
vals.remove("")
return vals