Version 1.0.2
- Update version - Add initial login variable in class to track initial login - added logic to only `super().authenticate` on first authentication - Store credentials in class for refresh tokens - Store cookies in class for later possible use - Only raise cookie error on first login - Dropped token expiry from 5 minutes to 4 - Update season number to reflect first season if it is the only season for the series - added authenticate method on each license request
This commit is contained in:
		
							parent
							
								
									192a915834
								
							
						
					
					
						commit
						9349d46546
					
				
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -1 +1,2 @@
 | 
				
			|||||||
*.pyc
 | 
					*.pyc
 | 
				
			||||||
 | 
					/EXAMPLE
 | 
				
			||||||
 | 
				
			|||||||
@ -25,7 +25,7 @@ class CR(Service):
 | 
				
			|||||||
    """
 | 
					    """
 | 
				
			||||||
    Service code for Crunchyroll
 | 
					    Service code for Crunchyroll
 | 
				
			||||||
    Author: TPD94
 | 
					    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.
 | 
					    Authorization: Cookies for web endpoints, Credentials for TV endpoints, Cookies/Credentials for both. Cookies required.
 | 
				
			||||||
    Security: FHD@L3
 | 
					    Security: FHD@L3
 | 
				
			||||||
    Use Series ID/URL (for example - https://www.crunchyroll.com/series/GG5H5XQ7D/kaiju-no-8) or Series ID (for example - GG5H5XQ7D).
 | 
					    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="""
 | 
					                   help="""
 | 
				
			||||||
                       Service code for Crunchyroll\n
 | 
					                       Service code for Crunchyroll\n
 | 
				
			||||||
                       Author: TPD94\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
 | 
					                       Authorization: Cookies for web endpoints, Credentials for TV endpoints, Cookies/Credentials for both. Cookies required.\n
 | 
				
			||||||
                       Security: FHD@L3\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).
 | 
					                       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.credential = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.initial_login = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_session(self):
 | 
					    def get_session(self):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Create a session using curl_cffi as it can impersonate browsers and avoid bot detection by Crunchyroll
 | 
					        # Create a session using curl_cffi as it can impersonate browsers and avoid bot detection by Crunchyroll
 | 
				
			||||||
        return session("chrome124")
 | 
					        return session("chrome124")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def authenticate(self, cookies: Optional[CookieJar] = None, credential: Optional[Credential] = None) -> None:
 | 
					    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
 | 
					        # 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
 | 
					        # 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.")
 | 
					            raise EnvironmentError("Service requires cookies for authentication.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # If authenticate is being called for the first time and cookies are present, retrieve an authorization token
 | 
					        # 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']
 | 
					            ).json()['access_token']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Update the token expiry, Crunchyroll offers 15 minutes between expiry.
 | 
					            # Update the token expiry, Crunchyroll offers 5 minutes between expiry.
 | 
				
			||||||
            self.auth_token_expiry_web = datetime.now() + timedelta(minutes=5)
 | 
					            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")
 | 
					            console.log("Only cookies detected, can only fetch web manifests")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if credential and self.auth_token_tv is None:
 | 
					        if credential and self.auth_token_tv is None:
 | 
				
			||||||
@ -141,8 +145,8 @@ class CR(Service):
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            ).json()['access_token']
 | 
					            ).json()['access_token']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Update the token expiry, Crunchyroll offers 15 minutes between expiry.
 | 
					            # Update the token expiry, Crunchyroll offers 5 minutes between expiry.
 | 
				
			||||||
            self.auth_token_expiry_tv = datetime.now() + timedelta(minutes=5)
 | 
					            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
 | 
					        # 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']
 | 
					            self.auth_token_web = refresh_response.json()['access_token']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Update the token expiry time
 | 
					            # 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 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:
 | 
					        if self.auth_token_tv is not None and datetime.now() > self.auth_token_expiry_tv:
 | 
				
			||||||
@ -178,8 +182,8 @@ class CR(Service):
 | 
				
			|||||||
                },
 | 
					                },
 | 
				
			||||||
                data={
 | 
					                data={
 | 
				
			||||||
                    'grant_type': 'password',
 | 
					                    'grant_type': 'password',
 | 
				
			||||||
                    'username': credential.username,
 | 
					                    'username': self.credential.username,
 | 
				
			||||||
                    'password': credential.password,
 | 
					                    'password': self.credential.password,
 | 
				
			||||||
                    'scope': 'offline_access',
 | 
					                    'scope': 'offline_access',
 | 
				
			||||||
                    'client_id': 'anydazwaxclrocanwho3',
 | 
					                    'client_id': 'anydazwaxclrocanwho3',
 | 
				
			||||||
                    'client_secret': '88gnIsucV-Q7sYrY29uOW_JGlMqx1mBN',
 | 
					                    'client_secret': '88gnIsucV-Q7sYrY29uOW_JGlMqx1mBN',
 | 
				
			||||||
@ -193,7 +197,7 @@ class CR(Service):
 | 
				
			|||||||
            self.auth_token_tv = refresh_response.json()['access_token']
 | 
					            self.auth_token_tv = refresh_response.json()['access_token']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Update the token expiry time
 | 
					            # 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:
 | 
					    def get_titles(self) -> Titles_T:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -241,7 +245,7 @@ class CR(Service):
 | 
				
			|||||||
                    id_=episode['id'],
 | 
					                    id_=episode['id'],
 | 
				
			||||||
                    service=self.__class__,
 | 
					                    service=self.__class__,
 | 
				
			||||||
                    title=episode['series_title'],
 | 
					                    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,
 | 
					                    number = episode['episode_number'] if isinstance(episode['episode_number'], int) else special_counter,
 | 
				
			||||||
                    name=episode['title'] if episode['title'] else episode['season_title'],
 | 
					                    name=episode['title'] if episode['title'] else episode['season_title'],
 | 
				
			||||||
                    year=episode['episode_air_date'][:4],
 | 
					                    year=episode['episode_air_date'][:4],
 | 
				
			||||||
@ -592,6 +596,7 @@ class CR(Service):
 | 
				
			|||||||
        return chapters
 | 
					        return chapters
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_widevine_license(self, *, challenge: bytes, title: Title_T, track: AnyTrack) -> Optional[Union[bytes, str]]:
 | 
					    def get_widevine_license(self, *, challenge: bytes, title: Title_T, track: AnyTrack) -> Optional[Union[bytes, str]]:
 | 
				
			||||||
 | 
					        self.authenticate()
 | 
				
			||||||
        if track.data['endpoint_type'] == 'tv':
 | 
					        if track.data['endpoint_type'] == 'tv':
 | 
				
			||||||
            # Get the episode response
 | 
					            # Get the episode response
 | 
				
			||||||
            episode_response = self.session.get(
 | 
					            episode_response = self.session.get(
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user