diff --git a/.gitignore b/.gitignore index 0d20b64..46d6192 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.pyc +/EXAMPLE diff --git a/CR/__init__.py b/CR/__init__.py index bd2278a..1ef8a18 100644 --- a/CR/__init__.py +++ b/CR/__init__.py @@ -25,7 +25,7 @@ class CR(Service): """ Service code for Crunchyroll Author: TPD94 - Version: 1.0.1 + Version: 1.0.2 Authorization: Cookies for web endpoints, Credentials for TV endpoints, Cookies/Credentials for both. Cookies required. Security: FHD@L3 Use Series ID/URL (for example - https://www.crunchyroll.com/series/GG5H5XQ7D/kaiju-no-8) or Series ID (for example - GG5H5XQ7D). @@ -36,7 +36,7 @@ class CR(Service): help=""" Service code for Crunchyroll\n Author: TPD94\n - Version: 1.0.1\n + Version: 1.0.2\n Authorization: Cookies for web endpoints, Credentials for TV endpoints, Cookies/Credentials for both. Cookies required.\n Security: FHD@L3\n Use Series ID/URL (for example - https://www.crunchyroll.com/series/GG5H5XQ7D/kaiju-no-8) or Series ID (for example - GG5H5XQ7D). @@ -73,27 +73,31 @@ class CR(Service): self.credential = None + self.initial_login = False + def get_session(self): # Create a session using curl_cffi as it can impersonate browsers and avoid bot detection by Crunchyroll return session("chrome124") def authenticate(self, cookies: Optional[CookieJar] = None, credential: Optional[Credential] = None) -> None: - if cookies: - self.cookies = cookies - elif hasattr(self, 'cookies'): - cookies = self.cookies - - if credential: - self.credential = credential - elif hasattr(self, 'credential'): - credential = self.credential - # Run the super method to load the cookies without writing redundant code - super().authenticate(cookies, credential) + if not self.initial_login: + super().authenticate(cookies, credential) + if cookies: + self.cookies = cookies + elif hasattr(self, 'cookies'): + cookies = self.cookies + + if credential: + self.credential = credential + elif hasattr(self, 'credential'): + credential = self.credential + + self.initial_login = True # Raise error if no cookies, Crunchyroll has implemented recaptcha, so authorization via credentials is not implemented - if not cookies: + if not cookies and not self.initial_login: raise EnvironmentError("Service requires cookies for authentication.") # If authenticate is being called for the first time and cookies are present, retrieve an authorization token @@ -115,10 +119,10 @@ class CR(Service): } ).json()['access_token'] - # Update the token expiry, Crunchyroll offers 15 minutes between expiry. - self.auth_token_expiry_web = datetime.now() + timedelta(minutes=5) + # Update the token expiry, Crunchyroll offers 5 minutes between expiry. + self.auth_token_expiry_web = datetime.now() + timedelta(minutes=4) - if not credential: + if not credential and not self.initial_login: console.log("Only cookies detected, can only fetch web manifests") if credential and self.auth_token_tv is None: @@ -141,8 +145,8 @@ class CR(Service): } ).json()['access_token'] - # Update the token expiry, Crunchyroll offers 15 minutes between expiry. - self.auth_token_expiry_tv = datetime.now() + timedelta(minutes=5) + # Update the token expiry, Crunchyroll offers 5 minutes between expiry. + self.auth_token_expiry_tv = datetime.now() + timedelta(minutes=4) # If there is already an authorization token for web, and it is expired, get a new one @@ -165,7 +169,7 @@ class CR(Service): self.auth_token_web = refresh_response.json()['access_token'] # Update the token expiry time - self.auth_token_expiry_web = datetime.now() + timedelta(minutes=5) + self.auth_token_expiry_web = datetime.now() + timedelta(minutes=4) # If there is already an authorization token for TV, and it is expired, get a new one if self.auth_token_tv is not None and datetime.now() > self.auth_token_expiry_tv: @@ -178,8 +182,8 @@ class CR(Service): }, data={ 'grant_type': 'password', - 'username': credential.username, - 'password': credential.password, + 'username': self.credential.username, + 'password': self.credential.password, 'scope': 'offline_access', 'client_id': 'anydazwaxclrocanwho3', 'client_secret': '88gnIsucV-Q7sYrY29uOW_JGlMqx1mBN', @@ -193,7 +197,7 @@ class CR(Service): self.auth_token_tv = refresh_response.json()['access_token'] # Update the token expiry time - self.auth_token_expiry_tv = datetime.now() + timedelta(minutes=5) + self.auth_token_expiry_tv = datetime.now() + timedelta(minutes=4) def get_titles(self) -> Titles_T: @@ -241,7 +245,7 @@ class CR(Service): id_=episode['id'], service=self.__class__, title=episode['series_title'], - season=int(episode['season_display_number']) if episode['season_display_number'] != '' else episode['season_sequence_number'] if episode['season_display_number'] == '' and episode['season_sequence_number'] == 1 else 0, + season=int(episode['season_display_number']) if episode['season_display_number'] != '' else episode['season_sequence_number'] if episode['season_display_number'] == '' and episode['season_sequence_number'] == 1 else 1 if episode['season_sequence_number'] == 0 else 0, number = episode['episode_number'] if isinstance(episode['episode_number'], int) else special_counter, name=episode['title'] if episode['title'] else episode['season_title'], year=episode['episode_air_date'][:4], @@ -592,6 +596,7 @@ class CR(Service): return chapters def get_widevine_license(self, *, challenge: bytes, title: Title_T, track: AnyTrack) -> Optional[Union[bytes, str]]: + self.authenticate() if track.data['endpoint_type'] == 'tv': # Get the episode response episode_response = self.session.get(