@@ -10,23 +10,27 @@ from .const import API_ENDPOINTS
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Custom exceptions
|
||||
|
||||
class AdGuardHomeError(Exception):
|
||||
"""Base exception for AdGuard Home API."""
|
||||
pass
|
||||
|
||||
|
||||
class AdGuardConnectionError(AdGuardHomeError):
|
||||
"""Exception for connection errors."""
|
||||
pass
|
||||
|
||||
|
||||
class AdGuardAuthError(AdGuardHomeError):
|
||||
"""Exception for authentication errors."""
|
||||
pass
|
||||
|
||||
|
||||
class AdGuardNotFoundError(AdGuardHomeError):
|
||||
"""Exception for not found errors."""
|
||||
pass
|
||||
|
||||
|
||||
class AdGuardHomeAPI:
|
||||
"""API wrapper for AdGuard Home."""
|
||||
|
||||
@@ -71,7 +75,7 @@ class AdGuardHomeAPI:
|
||||
return self._session
|
||||
|
||||
async def _request(self, method: str, endpoint: str, data: Optional[Dict] = None) -> Dict[str, Any]:
|
||||
"""Make an API request with comprehensive error handling."""
|
||||
"""Make an API request."""
|
||||
url = f"{self.base_url}{endpoint}"
|
||||
headers = {"Content-Type": "application/json"}
|
||||
auth = None
|
||||
@@ -84,11 +88,8 @@ class AdGuardHomeAPI:
|
||||
method, url, json=data, headers=headers, auth=auth
|
||||
) as response:
|
||||
|
||||
# Handle different HTTP status codes
|
||||
if response.status == 401:
|
||||
raise AdGuardAuthError("Authentication failed - check username/password")
|
||||
elif response.status == 403:
|
||||
raise AdGuardAuthError("Access forbidden - insufficient permissions")
|
||||
raise AdGuardAuthError("Authentication failed")
|
||||
elif response.status == 404:
|
||||
raise AdGuardNotFoundError(f"Endpoint not found: {endpoint}")
|
||||
elif response.status >= 500:
|
||||
@@ -96,24 +97,20 @@ class AdGuardHomeAPI:
|
||||
|
||||
response.raise_for_status()
|
||||
|
||||
# Handle empty responses
|
||||
if response.status == 204 or not response.content_length:
|
||||
return {}
|
||||
|
||||
try:
|
||||
return await response.json()
|
||||
except aiohttp.ContentTypeError:
|
||||
# Handle non-JSON responses
|
||||
text = await response.text()
|
||||
_LOGGER.warning("Non-JSON response received: %s", text)
|
||||
return {"response": text}
|
||||
|
||||
except asyncio.TimeoutError as err:
|
||||
raise AdGuardConnectionError(f"Timeout connecting to AdGuard Home: {err}")
|
||||
raise AdGuardConnectionError(f"Timeout: {err}")
|
||||
except ClientError as err:
|
||||
raise AdGuardConnectionError(f"Client error: {err}")
|
||||
except Exception as err:
|
||||
_LOGGER.error("Unexpected error communicating with AdGuard Home: %s", err)
|
||||
raise AdGuardHomeError(f"Unexpected error: {err}")
|
||||
|
||||
async def test_connection(self) -> bool:
|
||||
@@ -121,8 +118,7 @@ class AdGuardHomeAPI:
|
||||
try:
|
||||
await self._request("GET", API_ENDPOINTS["status"])
|
||||
return True
|
||||
except Exception as err:
|
||||
_LOGGER.debug("Connection test failed: %s", err)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
async def get_status(self) -> Dict[str, Any]:
|
||||
@@ -144,7 +140,6 @@ class AdGuardHomeAPI:
|
||||
|
||||
async def add_client(self, client_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Add a new client configuration."""
|
||||
# Validate required fields
|
||||
if "name" not in client_data:
|
||||
raise ValueError("Client name is required")
|
||||
if "ids" not in client_data or not client_data["ids"]:
|
||||
@@ -155,9 +150,9 @@ class AdGuardHomeAPI:
|
||||
async def update_client(self, client_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Update an existing client configuration."""
|
||||
if "name" not in client_data:
|
||||
raise ValueError("Client name is required for update")
|
||||
raise ValueError("Client name is required")
|
||||
if "data" not in client_data:
|
||||
raise ValueError("Client data is required for update")
|
||||
raise ValueError("Client data is required")
|
||||
|
||||
return await self._request("POST", API_ENDPOINTS["clients_update"], client_data)
|
||||
|
||||
@@ -183,15 +178,13 @@ class AdGuardHomeAPI:
|
||||
return client
|
||||
|
||||
return None
|
||||
except Exception as err:
|
||||
_LOGGER.error("Failed to get client %s: %s", client_name, err)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
async def update_client_blocked_services(
|
||||
self,
|
||||
client_name: str,
|
||||
blocked_services: list,
|
||||
schedule: Optional[Dict[str, Any]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""Update blocked services for a specific client."""
|
||||
if not client_name:
|
||||
@@ -201,21 +194,11 @@ class AdGuardHomeAPI:
|
||||
if not client:
|
||||
raise AdGuardNotFoundError(f"Client '{client_name}' not found")
|
||||
|
||||
# Prepare the blocked services data with proper structure
|
||||
if schedule:
|
||||
blocked_services_data = {
|
||||
"ids": blocked_services,
|
||||
"schedule": schedule
|
||||
}
|
||||
else:
|
||||
blocked_services_data = {
|
||||
"ids": blocked_services,
|
||||
"schedule": {
|
||||
"time_zone": "Local"
|
||||
}
|
||||
}
|
||||
blocked_services_data = {
|
||||
"ids": blocked_services,
|
||||
"schedule": {"time_zone": "Local"}
|
||||
}
|
||||
|
||||
# Update the client with new blocked services
|
||||
update_data = {
|
||||
"name": client_name,
|
||||
"data": {
|
||||
@@ -226,37 +209,6 @@ class AdGuardHomeAPI:
|
||||
|
||||
return await self.update_client(update_data)
|
||||
|
||||
async def toggle_client_service(
|
||||
self, client_name: str, service_id: str, enabled: bool
|
||||
) -> Dict[str, Any]:
|
||||
"""Toggle a specific service for a client."""
|
||||
if not client_name or not service_id:
|
||||
raise ValueError("Client name and service ID are required")
|
||||
|
||||
client = await self.get_client_by_name(client_name)
|
||||
if not client:
|
||||
raise AdGuardNotFoundError(f"Client '{client_name}' not found")
|
||||
|
||||
# Get current blocked services
|
||||
blocked_services = client.get("blocked_services", {})
|
||||
if isinstance(blocked_services, dict):
|
||||
service_ids = blocked_services.get("ids", [])
|
||||
else:
|
||||
# Handle legacy format (direct list)
|
||||
service_ids = blocked_services if blocked_services else []
|
||||
|
||||
# Update the service list
|
||||
if enabled and service_id not in service_ids:
|
||||
service_ids.append(service_id)
|
||||
elif not enabled and service_id in service_ids:
|
||||
service_ids.remove(service_id)
|
||||
|
||||
return await self.update_client_blocked_services(client_name, service_ids)
|
||||
|
||||
async def get_blocked_services(self) -> Dict[str, Any]:
|
||||
"""Get available blocked services."""
|
||||
return await self._request("GET", API_ENDPOINTS["blocked_services_all"])
|
||||
|
||||
async def close(self) -> None:
|
||||
"""Close the API session if we own it."""
|
||||
if self._own_session and self._session:
|
||||
|
Reference in New Issue
Block a user