diff --git a/pyproject.toml b/pyproject.toml index c18eb67f..2e39fb9d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ pathvalidate = "^2.4.1" simple-term-menu = { version = "^1.2.1", platform = 'darwin|linux' } pick = { version = "^2", platform = 'win32|cygwin' } windows-curses = { version = "^2.2.0", platform = 'win32|cygwin' } -Pillow = ">=9,<11" +Pillow = ">=9" deezer-py = "1.3.6" pycryptodomex = "^3.10.1" appdirs = "^1.4.4" diff --git a/streamrip/client/qobuz.py b/streamrip/client/qobuz.py index 734e2b82..e75895df 100644 --- a/streamrip/client/qobuz.py +++ b/streamrip/client/qobuz.py @@ -171,13 +171,33 @@ async def login(self): if not c.app_id or not c.secrets: logger.info("App id/secrets not found, fetching") - c.app_id, c.secrets = await self._get_app_id_and_secrets() - # write to file - f = self.config.file - f.qobuz.app_id = c.app_id - f.qobuz.secrets = c.secrets - f.set_modified() + await self._refresh_app_id_and_secrets() + + # A stale app_id/secret pair (e.g. hardcoded in the config after Qobuz + # rotated its app secret) fails with InvalidAppIdError or + # InvalidAppSecretError. Re-fetch a fresh pair from the bundle and retry + # once before giving up. + try: + await self._attempt_login() + except (InvalidAppIdError, InvalidAppSecretError) as e: + logger.warning("Login failed with %s, refetching app id/secrets", e) + self.session.headers.pop("X-User-Auth-Token", None) + await self._refresh_app_id_and_secrets() + await self._attempt_login() + self.logged_in = True + + async def _refresh_app_id_and_secrets(self): + c = self.config.session.qobuz + c.app_id, c.secrets = await self._get_app_id_and_secrets() + # write to file + f = self.config.file + f.qobuz.app_id = c.app_id + f.qobuz.secrets = c.secrets + f.set_modified() + + async def _attempt_login(self): + c = self.config.session.qobuz self.session.headers.update({"X-App-Id": str(c.app_id)}) if c.use_auth_token: @@ -212,8 +232,6 @@ async def login(self): self.secret = await self._get_valid_secret(c.secrets) - self.logged_in = True - async def get_metadata(self, item: str, media_type: str): if media_type == "label": return await self.get_label(item)