================================================================================ CRUNCHYROLL DOWNLOADER - CHALLENGE 1 SOLUTION Chapter 4: Building Utilities Challenge: Build CrunchyrollClient ================================================================================ PROBLEM: Create class with __init__, _get(), and get_episodes() methods. SOLUTION: ================================================================================ File: crunchyroll_utils.py (CrunchyrollClient class) import requests class CrunchyrollClient: """Reusable Crunchyroll API client.""" def __init__(self, config, token: str): """Initialize client with configuration and JWT token.""" self.config = config self.token = token self.base_url = config.api_base self.episodes_fetched = 0 self.headers = { "Authorization": f"Bearer {token}", "User-Agent": "Mozilla/5.0" } def _get(self, endpoint: str) -> dict: """Make authenticated GET request to endpoint.""" url = f"{self.base_url}{endpoint}" try: response = requests.get(url, headers=self.headers, timeout=10) if response.status_code == 200: return response.json() elif response.status_code == 401: print("❌ Token expired or invalid") return None else: print(f"❌ Request failed: {response.status_code}") return None except Exception as e: print(f"❌ Error: {e}") return None def get_episodes(self) -> list: """Fetch all watched episodes.""" print("📡 Fetching episodes...") try: data = self._get("/api/v2/episodes") if not data: return [] episodes = data.get('items', []) self.episodes_fetched = len(episodes) print(f"✅ Fetched {self.episodes_fetched} episodes") return episodes except Exception as e: print(f"❌ Error: {e}") return [] USAGE ================================================================================ from config import Config from crunchyroll_utils import login, load_cached_token, CrunchyrollClient config = Config() token = load_cached_token(config.token_cache) if not token: token = login(config) # Create client client = CrunchyrollClient(config, token) # Fetch episodes episodes = client.get_episodes() for episode in episodes[:5]: # Show first 5 print(f"- {episode['title']}") CLASS BENEFITS ================================================================================ ✅ Encapsulation: All API logic in one place ✅ Reusability: Can use same client for multiple endpoints ✅ State tracking: Tracks how many episodes fetched ✅ Error handling: Consistent error handling ✅ Testable: Easy to test and mock ================================================================================