@@ -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
92101class Config :
@@ -600,16 +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- json_obj : JsonObj = self ._login_with_link ()
619+ link_login : LinkLogin = self .get_link_login ()
610620 executor = concurrent .futures .ThreadPoolExecutor ()
611621
612- return LinkLogin ( json_obj ) , executor .submit (self ._process_link_login , json_obj )
622+ return link_login , executor .submit (self .process_link_login , link_login )
613623
614624 def save_session_to_file (self , session_file : Path ):
615625 # create a new session
@@ -646,7 +656,13 @@ def load_session_from_file(self, session_file: Path):
646656
647657 return self .load_oauth_session (** args )
648658
649- def _login_with_link (self ) -> JsonObj :
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+ """
650666 url = "https://auth.tidal.com/v1/oauth2/device_authorization"
651667 params = {"client_id" : self .config .client_id , "scope" : "r_usr w_usr w_sub" }
652668
@@ -658,23 +674,39 @@ def _login_with_link(self) -> JsonObj:
658674
659675 json = request .json ()
660676
661- return json
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 )
662695
663- def _process_link_login (self , json : JsonObj ) -> None :
664- json = self ._wait_for_link_login (json )
665- self .process_auth_token (json , is_pkce_token = False )
696+ return result_process
666697
667698 def process_auth_token (
668699 self , json : dict [str , Union [str , int ]], is_pkce_token : bool = True
669- ) -> None :
700+ ) -> bool :
670701 """Parses the authorization response and sets the token values to the specific
671702 variables for further usage.
672703
673704 :param json: Parsed JSON response after login / authorization.
674705 :type json: dict[str, str | int]
675706 :param is_pkce_token: Set true if current token is obtained using PKCE
676707 :type is_pkce_token: bool
677- :return: None
708+ :return: `True` if no error occurs.
709+ :rtype: bool
678710 """
679711 self .access_token = json ["access_token" ]
680712 self .expiry_time = datetime .datetime .utcnow () + datetime .timedelta (
@@ -689,28 +721,45 @@ def process_auth_token(
689721 self .user = user .User (self , user_id = json ["userId" ]).factory ()
690722 self .is_pkce = is_pkce_token
691723
692- def _wait_for_link_login (self , json : JsonObj ) -> Any :
693- expiry = float (json ["expiresIn" ])
694- interval = float (json ["interval" ])
695- 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
696741 url = self .config .api_oauth2_token
697742 params = {
698743 "client_id" : self .config .client_id ,
699744 "client_secret" : self .config .client_secret ,
700- "device_code" : device_code ,
745+ "device_code" : link_login . device_code ,
701746 "grant_type" : "urn:ietf:params:oauth:grant-type:device_code" ,
702747 "scope" : "r_usr w_usr w_sub" ,
703748 }
749+
704750 while expiry > 0 :
705751 request = self .request_session .post (url , params )
706- json = request .json ()
752+ result : JsonObj = request .json ()
753+
707754 if request .ok :
708- return json
755+ return result
756+
709757 # Because the requests take time, the expiry variable won't be accurate, so stop if TIDAL says it's expired
710- if json ["error" ] == "expired_token" :
758+ if result ["error" ] == "expired_token" :
711759 break
712- time .sleep (interval )
713- expiry = expiry - interval
760+
761+ time .sleep (link_login .interval )
762+ expiry = expiry - link_login .interval
714763
715764 raise TimeoutError ("You took too long to log in" )
716765
0 commit comments