import requests import logging import traceback import json logger = logging.getLogger(__name__) class Client: """Xtream Codes API Client with robust error handling""" def __init__(self, server_url, username, password, user_agent=None): self.server_url = self._normalize_url(server_url) self.username = username self.password = password self.user_agent = user_agent # Fix: Properly handle all possible user_agent input types if user_agent: if isinstance(user_agent, str): # Direct string user agent user_agent_string = user_agent elif hasattr(user_agent, 'user_agent'): # UserAgent model object user_agent_string = user_agent.user_agent else: # Fallback for any other type logger.warning(f"Unexpected user_agent type: {type(user_agent)}, using default") user_agent_string = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)' else: # No user agent provided user_agent_string = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)' self.headers = {'User-Agent': user_agent_string} self.server_info = None def _normalize_url(self, url): """Normalize server URL by removing trailing slashes and paths""" if not url: raise ValueError("Server URL cannot be empty") url = url.rstrip('/') # Remove any path after domain - we'll construct proper API URLs # Split by protocol first to preserve it if '://' in url: protocol, rest = url.split('://', 1) domain = rest.split('/', 1)[0] return f"{protocol}://{domain}" return url def _make_request(self, endpoint, params=None): """Make request with detailed error handling""" try: url = f"{self.server_url}/{endpoint}" logger.debug(f"XC API Request: {url} with params: {params}") response = requests.get(url, params=params, headers=self.headers, timeout=30) response.raise_for_status() data = response.json() logger.debug(f"XC API Response: {url} status code: {response.status_code}") # Check for XC-specific error responses if isinstance(data, dict) and data.get('user_info') is None and 'error' in data: error_msg = f"XC API Error: {data.get('error', 'Unknown error')}" logger.error(error_msg) raise ValueError(error_msg) return data except requests.RequestException as e: error_msg = f"XC API Request failed: {str(e)}" logger.error(error_msg) logger.error(f"Request details: URL={url}, Params={params}") raise except ValueError as e: # This could be from JSON parsing or our explicit raises logger.error(f"XC API Invalid response: {str(e)}") raise except Exception as e: logger.error(f"XC API Unexpected error: {str(e)}") logger.error(traceback.format_exc()) raise def authenticate(self): """Authenticate and validate server response""" try: endpoint = "player_api.php" params = { 'username': self.username, 'password': self.password } self.server_info = self._make_request(endpoint, params) if not self.server_info or not self.server_info.get('user_info'): error_msg = "Authentication failed: Invalid response from server" logger.error(f"{error_msg}. Response: {self.server_info}") raise ValueError(error_msg) logger.info(f"XC Authentication successful for user {self.username}") return self.server_info except Exception as e: logger.error(f"XC Authentication failed: {str(e)}") logger.error(traceback.format_exc()) raise def get_live_categories(self): """Get live TV categories""" try: if not self.server_info: self.authenticate() endpoint = "player_api.php" params = { 'username': self.username, 'password': self.password, 'action': 'get_live_categories' } categories = self._make_request(endpoint, params) if not isinstance(categories, list): error_msg = f"Invalid categories response: {categories}" logger.error(error_msg) raise ValueError(error_msg) logger.info(f"Successfully retrieved {len(categories)} live categories") logger.debug(f"Categories: {json.dumps(categories[:5])}...") return categories except Exception as e: logger.error(f"Failed to get live categories: {str(e)}") logger.error(traceback.format_exc()) raise def get_live_category_streams(self, category_id): """Get streams for a specific category""" try: if not self.server_info: self.authenticate() endpoint = "player_api.php" params = { 'username': self.username, 'password': self.password, 'action': 'get_live_streams', 'category_id': category_id } streams = self._make_request(endpoint, params) if not isinstance(streams, list): error_msg = f"Invalid streams response for category {category_id}: {streams}" logger.error(error_msg) raise ValueError(error_msg) logger.info(f"Successfully retrieved {len(streams)} streams for category {category_id}") return streams except Exception as e: logger.error(f"Failed to get streams for category {category_id}: {str(e)}") logger.error(traceback.format_exc()) raise def get_stream_url(self, stream_id): """Get the playback URL for a stream""" return f"{self.server_url}/live/{self.username}/{self.password}/{stream_id}.ts"