mirror of
https://github.com/nathom/streamrip.git
synced 2025-05-13 14:44:49 -04:00
Use asynchronous requests for QobuzClient startup/login
This commit is contained in:
parent
e9f40923ba
commit
6e0731ed0b
4 changed files with 54 additions and 36 deletions
17
rip/cli.py
17
rip/cli.py
|
@ -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
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue