Skip to content

Commit 9c451c7

Browse files
authored
Merge pull request #303 from exislow/302-return-raw-values
Moved `LinkLogin` and `futures` to `login_oauth`.
2 parents 6e3a4e6 + 9163394 commit 9c451c7

File tree

1 file changed

+73
-22
lines changed

1 file changed

+73
-22
lines changed

tidalapi/session.py

+73-22
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,21 @@ class LinkLogin:
8181
verification_uri: str
8282
#: The link the user has to visit, with the code already included
8383
verification_uri_complete: str
84+
#: After how much time the uri expires.
85+
expires_in: float
86+
#: The interval for authorization checks against the backend.
87+
interval: float
88+
#: The unique device code necessary for authorization.
89+
device_code: str
8490

8591
def __init__(self, json: JsonObj):
8692
self.expires_in = int(json["expiresIn"])
8793
self.user_code = str(json["userCode"])
8894
self.verification_uri = str(json["verificationUri"])
8995
self.verification_uri_complete = str(json["verificationUriComplete"])
96+
self.expires_in = float(json["expiresIn"])
97+
self.interval = float(json["interval"])
98+
self.device_code = str(json["deviceCode"])
9099

91100

92101
class Config:
@@ -600,14 +609,17 @@ def login_oauth_simple(self, fn_print: Callable[[str], None] = print) -> None:
600609
def login_oauth(self) -> Tuple[LinkLogin, concurrent.futures.Future[Any]]:
601610
"""Login to TIDAL with a remote link for limited input devices. The function
602611
will return everything you need to log in through a web browser, and will return
603-
an future that will run until login.
612+
a future that will run until login.
604613
605614
:return: A :class:`LinkLogin` object containing all the data needed to log in remotely, and
606-
a :class:`concurrent.futures.Future` that will poll until the login is completed, or until the link expires.
615+
a :class:`concurrent.futures.Future` object that will poll until the login is completed, or until the link expires.
616+
:rtype: :class:`LinkLogin`
607617
:raises: TimeoutError: If the login takes too long
608618
"""
609-
login, future = self._login_with_link()
610-
return login, future
619+
link_login: LinkLogin = self.get_link_login()
620+
executor = concurrent.futures.ThreadPoolExecutor()
621+
622+
return link_login, executor.submit(self.process_link_login, link_login)
611623

612624
def save_session_to_file(self, session_file: Path):
613625
# create a new session
@@ -644,7 +656,13 @@ def load_session_from_file(self, session_file: Path):
644656

645657
return self.load_oauth_session(**args)
646658

647-
def _login_with_link(self) -> Tuple[LinkLogin, concurrent.futures.Future[Any]]:
659+
def get_link_login(self) -> LinkLogin:
660+
"""Return information required to login into TIDAL using a device authorization
661+
link.
662+
663+
:return: Login information for device authorization retrieved from the TIDAL backend.
664+
:rtype: :class:`LinkLogin`
665+
"""
648666
url = "https://auth.tidal.com/v1/oauth2/device_authorization"
649667
params = {"client_id": self.config.client_id, "scope": "r_usr w_usr w_sub"}
650668

@@ -655,24 +673,40 @@ def _login_with_link(self) -> Tuple[LinkLogin, concurrent.futures.Future[Any]]:
655673
request.raise_for_status()
656674

657675
json = request.json()
658-
executor = concurrent.futures.ThreadPoolExecutor()
659-
return LinkLogin(json), executor.submit(self._process_link_login, json)
660676

661-
def _process_link_login(self, json: JsonObj) -> None:
662-
json = self._wait_for_link_login(json)
663-
self.process_auth_token(json, is_pkce_token=False)
677+
return LinkLogin(json)
678+
679+
def process_link_login(
680+
self, link_login: LinkLogin, until_expiry: bool = True
681+
) -> bool:
682+
"""Checks if device authorization was successful and processes the retrieved
683+
OAuth token from the Backend.
684+
685+
:param link_login: Link login information containing the necessary device authorization information.
686+
:type link_login: :class:`LinkLogin`
687+
:param until_expiry: If `True` device authorization check is running until the link expires. If `False`check is running only once.
688+
:type until_expiry: :class:`bool`
689+
690+
:return: `True` if login was successful.
691+
:rtype: bool
692+
"""
693+
result: JsonObj = self._check_link_login(link_login, until_expiry)
694+
result_process: bool = self.process_auth_token(result, is_pkce_token=False)
695+
696+
return result_process
664697

665698
def process_auth_token(
666699
self, json: dict[str, Union[str, int]], is_pkce_token: bool = True
667-
) -> None:
700+
) -> bool:
668701
"""Parses the authorization response and sets the token values to the specific
669702
variables for further usage.
670703
671704
:param json: Parsed JSON response after login / authorization.
672705
:type json: dict[str, str | int]
673706
:param is_pkce_token: Set true if current token is obtained using PKCE
674707
:type is_pkce_token: bool
675-
:return: None
708+
:return: `True` if no error occurs.
709+
:rtype: bool
676710
"""
677711
self.access_token = json["access_token"]
678712
self.expiry_time = datetime.datetime.utcnow() + datetime.timedelta(
@@ -687,28 +721,45 @@ def process_auth_token(
687721
self.user = user.User(self, user_id=json["userId"]).factory()
688722
self.is_pkce = is_pkce_token
689723

690-
def _wait_for_link_login(self, json: JsonObj) -> Any:
691-
expiry = float(json["expiresIn"])
692-
interval = float(json["interval"])
693-
device_code = json["deviceCode"]
724+
return True
725+
726+
def _check_link_login(
727+
self, link_login: LinkLogin, until_expiry: bool = True
728+
) -> TimeoutError | JsonObj:
729+
"""Checks if device authorization was successful and retrieves OAuth data. Can
730+
check the backend for successful device authrization until the link expires
731+
(with the given interval) or just once.
732+
733+
:param link_login: Link login information containing the necessary device authorization information.
734+
:type link_login: :class:`LinkLogin`
735+
:param until_expiry: If `True` device authorization check is running until the link expires. If `False`check is running only once.
736+
:type until_expiry: :class:`bool`
737+
:return: Raise :class:`TimeoutError` if the link has expired otherwise returns retrieved OAuth information.
738+
:rtype: :class:`TimeoutError` | :class:`JsonObj`
739+
"""
740+
expiry: float = link_login.expires_in if until_expiry else 1
694741
url = self.config.api_oauth2_token
695742
params = {
696743
"client_id": self.config.client_id,
697744
"client_secret": self.config.client_secret,
698-
"device_code": device_code,
745+
"device_code": link_login.device_code,
699746
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
700747
"scope": "r_usr w_usr w_sub",
701748
}
749+
702750
while expiry > 0:
703751
request = self.request_session.post(url, params)
704-
json = request.json()
752+
result: JsonObj = request.json()
753+
705754
if request.ok:
706-
return json
755+
return result
756+
707757
# Because the requests take time, the expiry variable won't be accurate, so stop if TIDAL says it's expired
708-
if json["error"] == "expired_token":
758+
if result["error"] == "expired_token":
709759
break
710-
time.sleep(interval)
711-
expiry = expiry - interval
760+
761+
time.sleep(link_login.interval)
762+
expiry = expiry - link_login.interval
712763

713764
raise TimeoutError("You took too long to log in")
714765

0 commit comments

Comments
 (0)