mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-01-23 02:35:14 +00:00
Enhance XCClient with robust error handling and user agent management; improve M3U processing logic for better error reporting and validation.
This commit is contained in:
parent
728ab45534
commit
92d09eea3b
2 changed files with 456 additions and 129 deletions
|
|
@ -1,39 +1,164 @@
|
|||
import requests
|
||||
import logging
|
||||
import traceback
|
||||
import json
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Client:
|
||||
host = ""
|
||||
username = ""
|
||||
password = ""
|
||||
user_agent = ""
|
||||
"""Xtream Codes API Client with robust error handling"""
|
||||
|
||||
def __init__(self, host, username, password, user_agent):
|
||||
self.host = host
|
||||
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
|
||||
|
||||
# Handle UserAgent objects by extracting the string value
|
||||
if hasattr(user_agent, 'user_agent'): # Check if it's a UserAgent model object
|
||||
self.user_agent = user_agent.user_agent # Extract the string attribute
|
||||
# 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:
|
||||
self.user_agent = str(user_agent) # Ensure it's a string in any case
|
||||
# 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):
|
||||
response = requests.get(f"{self.host}/player_api.php?username={self.username}&password={self.password}", headers={
|
||||
'User-Agent': self.user_agent,
|
||||
})
|
||||
return response.json()
|
||||
"""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):
|
||||
response = requests.get(f"{self.host}/player_api.php?username={self.username}&password={self.password}&action=get_live_categories", headers={
|
||||
'User-Agent': self.user_agent,
|
||||
})
|
||||
return response.json()
|
||||
"""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):
|
||||
response = requests.get(f"{self.host}/player_api.php?username={self.username}&password={self.password}&action=get_live_streams&category_id={category_id}", headers={
|
||||
'User-Agent': self.user_agent,
|
||||
})
|
||||
return response.json()
|
||||
"""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):
|
||||
return f"{self.host}/{self.username}/{self.password}/{stream_id}"
|
||||
"""Get the playback URL for a stream"""
|
||||
return f"{self.server_url}/live/{self.username}/{self.password}/{stream_id}.ts"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue