diff --git a/.gitea/workflows/integration-test.yml b/.gitea/workflows/integration-test.yml index 8f5644c..ac3f2b4 100644 --- a/.gitea/workflows/integration-test.yml +++ b/.gitea/workflows/integration-test.yml @@ -12,8 +12,8 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.11', '3.12'] - home-assistant-version: ['2023.12.0', '2024.1.0'] + python-version: ['3.9','3.10','3.11','3.12','3.13'] + home-assistant-version: ['2025.9.4'] steps: - name: 📥 Checkout Code diff --git a/.gitea/workflows/quality-check.yml b/.gitea/workflows/quality-check.yml index 234264d..d6d6e6f 100644 --- a/.gitea/workflows/quality-check.yml +++ b/.gitea/workflows/quality-check.yml @@ -24,7 +24,7 @@ jobs: run: | python -m pip install --upgrade pip pip install flake8 black isort mypy bandit safety - pip install homeassistant==2023.12.0 + pip install homeassistant==2025.9.4 pip install -r requirements-dev.txt || echo "No dev requirements found" - name: 🎨 Check Code Formatting (Black) diff --git a/custom_components/adguard_hub/__init__.py b/custom_components/adguard_hub/__init__.py index 7965dd3..5584aee 100644 --- a/custom_components/adguard_hub/__init__.py +++ b/custom_components/adguard_hub/__init__.py @@ -18,6 +18,7 @@ from .api import AdGuardHomeAPI _LOGGER = logging.getLogger(__name__) + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up AdGuard Control Hub from a config entry.""" session = async_get_clientsession(hass, entry.data.get(CONF_VERIFY_SSL, True)) @@ -34,8 +35,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # Test the connection try: await api.test_connection() - _LOGGER.info("Successfully connected to AdGuard Home at %s:%s", - entry.data[CONF_HOST], entry.data[CONF_PORT]) + _LOGGER.info("Successfully connected to AdGuard Home at %s:%s", + entry.data[CONF_HOST], entry.data[CONF_PORT]) except Exception as err: _LOGGER.error("Failed to connect to AdGuard Home: %s", err) raise ConfigEntryNotReady(f"Unable to connect: {err}") @@ -57,6 +58,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: _LOGGER.info("AdGuard Control Hub setup complete") return True + async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload AdGuard Control Hub config entry.""" unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) @@ -66,6 +68,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok + class AdGuardControlHubCoordinator(DataUpdateCoordinator): """AdGuard Control Hub data update coordinator.""" @@ -88,7 +91,7 @@ class AdGuardControlHubCoordinator(DataUpdateCoordinator): # Fetch all data concurrently for better performance results = await asyncio.gather( self.api.get_clients(), - self.api.get_statistics(), + self.api.get_statistics(), self.api.get_status(), return_exceptions=True, ) @@ -103,7 +106,7 @@ class AdGuardControlHubCoordinator(DataUpdateCoordinator): # Update stored data (use empty dict if fetch failed) self._clients = { - client["name"]: client + client["name"]: client for client in (clients.get("clients", []) if not isinstance(clients, Exception) else []) } self._statistics = statistics if not isinstance(statistics, Exception) else {} @@ -123,7 +126,7 @@ class AdGuardControlHubCoordinator(DataUpdateCoordinator): """Return clients data.""" return self._clients - @property + @property def statistics(self): """Return statistics data.""" return self._statistics @@ -131,4 +134,4 @@ class AdGuardControlHubCoordinator(DataUpdateCoordinator): @property def protection_status(self): """Return protection status data.""" - return self._protection_status \ No newline at end of file + return self._protection_status diff --git a/custom_components/adguard_hub/api.py b/custom_components/adguard_hub/api.py index 10ee8fb..34d18a2 100644 --- a/custom_components/adguard_hub/api.py +++ b/custom_components/adguard_hub/api.py @@ -7,11 +7,12 @@ from .const import API_ENDPOINTS _LOGGER = logging.getLogger(__name__) + class AdGuardHomeAPI: """API wrapper for AdGuard Home.""" - def __init__(self, host: str, port: int = 3000, username: str = None, - password: str = None, ssl: bool = False, session = None): + def __init__(self, host: str, port: int = 3000, username: str = None, + password: str = None, ssl: bool = False, session=None): self.host = host self.port = port self.username = username @@ -88,7 +89,8 @@ class AdGuardHomeAPI: return None - async def update_client_blocked_services(self, client_name: str, blocked_services: list, schedule: dict = None) -> dict: + async def update_client_blocked_services(self, client_name: str, blocked_services: list, + schedule: dict = None) -> dict: """Update blocked services for a specific client.""" client = await self.get_client_by_name(client_name) if not client: @@ -139,4 +141,4 @@ class AdGuardHomeAPI: 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) \ No newline at end of file + return await self.update_client_blocked_services(client_name, service_ids) diff --git a/custom_components/adguard_hub/manifest.json b/custom_components/adguard_hub/manifest.json index 19438e0..ed709bb 100644 --- a/custom_components/adguard_hub/manifest.json +++ b/custom_components/adguard_hub/manifest.json @@ -1,13 +1,12 @@ { "domain": "adguard_hub", "name": "AdGuard Control Hub", - "codeowners": ["@your-gitea-username"], + "codeowners": ["@sq4ind"], "config_flow": true, "dependencies": [], - "documentation": "https://your-gitea-domain.com/your-username/adguard-control-hub", + "documentation": "https://git.sq4ind.eu/sq4ind/adguard-control-hub", "integration_type": "hub", "iot_class": "local_polling", - "issue_tracker": "https://your-gitea-domain.com/your-username/adguard-control-hub/issues", "requirements": [ "aiohttp>=3.8.0" ], diff --git a/custom_components/adguard_hub/switch.py b/custom_components/adguard_hub/switch.py index 25f7d20..9c87400 100644 --- a/custom_components/adguard_hub/switch.py +++ b/custom_components/adguard_hub/switch.py @@ -11,6 +11,7 @@ from .const import DOMAIN, ICON_PROTECTION, ICON_PROTECTION_OFF, ICON_CLIENT, MA _LOGGER = logging.getLogger(__name__) + async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback): """Set up AdGuard Control Hub switch platform.""" coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"] @@ -26,6 +27,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, asyn async_add_entities(entities) + class AdGuardBaseSwitch(CoordinatorEntity, SwitchEntity): """Base class for AdGuard switches.""" @@ -39,6 +41,7 @@ class AdGuardBaseSwitch(CoordinatorEntity, SwitchEntity): "model": "AdGuard Home", } + class AdGuardProtectionSwitch(AdGuardBaseSwitch): """Switch to control global AdGuard protection.""" @@ -63,6 +66,7 @@ class AdGuardProtectionSwitch(AdGuardBaseSwitch): await self.api.set_protection(False) await self.coordinator.async_request_refresh() + class AdGuardClientSwitch(AdGuardBaseSwitch): """Switch to control client-specific protection.""" @@ -86,4 +90,4 @@ class AdGuardClientSwitch(AdGuardBaseSwitch): async def async_turn_off(self, **kwargs): # This would update client settings - simplified for basic functionality _LOGGER.info("Would disable protection for %s", self.client_name) - await self.coordinator.async_request_refresh() \ No newline at end of file + await self.coordinator.async_request_refresh()