Updated apps

This commit is contained in:
2026-07-20 22:52:35 -04:00
parent 28a8cb98f6
commit a0c3271743
1164 changed files with 94781 additions and 6892 deletions
@@ -0,0 +1 @@
"""Spook - Your homie."""
@@ -0,0 +1,40 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import area_registry as ar, config_validation as cv
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to add an alias to an area."""
domain = DOMAIN
service = "add_alias_to_area"
schema = {
vol.Required("area_id"): cv.string,
vol.Required("alias"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
area_registry = ar.async_get(self.hass)
if not (area := area_registry.async_get_area(call.data["area_id"])):
msg = f"Area {call.data['area_id']} not found"
raise HomeAssistantError(msg)
aliases = area.aliases.copy()
area_registry.async_update(
call.data["area_id"],
aliases=aliases.union(call.data["alias"]),
)
@@ -0,0 +1,43 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import (
config_validation as cv,
floor_registry as fr,
)
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to add an alias to a floor."""
domain = DOMAIN
service = "add_alias_to_floor"
schema = {
vol.Required("floor_id"): cv.string,
vol.Required("alias"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
floor_registry = fr.async_get(self.hass)
if not (floor := floor_registry.async_get_floor(call.data["floor_id"])):
msg = f"Floor {call.data['floor_id']} not found"
raise HomeAssistantError(msg)
aliases = floor.aliases.copy()
floor_registry.async_update(
call.data["floor_id"],
aliases=aliases.union(call.data["alias"]),
)
@@ -0,0 +1,45 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import (
area_registry as ar,
config_validation as cv,
floor_registry as fr,
)
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to add an area to a floor."""
domain = DOMAIN
service = "add_area_to_floor"
schema = {
vol.Required("floor_id"): cv.string,
vol.Required("area_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
floor_registry = fr.async_get(self.hass)
if not floor_registry.async_get_floor(call.data["floor_id"]):
msg = f"Floor {call.data['floor_id']} not found"
raise HomeAssistantError(msg)
area_registry = ar.async_get(self.hass)
for area_id in call.data["area_id"]:
area_registry.async_update(
area_id=area_id,
floor_id=call.data["floor_id"],
)
@@ -0,0 +1,45 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import (
area_registry as ar,
config_validation as cv,
device_registry as dr,
)
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to add a device to an area."""
domain = DOMAIN
service = "add_device_to_area"
schema = {
vol.Required("area_id"): cv.string,
vol.Required("device_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
area_registry = ar.async_get(self.hass)
if not area_registry.async_get_area(call.data["area_id"]):
msg = f"Area {call.data['area_id']} not found"
raise HomeAssistantError(msg)
device_registry = dr.async_get(self.hass)
for device_id in call.data["device_id"]:
device_registry.async_update_device(
device_id,
area_id=call.data["area_id"],
)
@@ -0,0 +1,45 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import (
area_registry as ar,
config_validation as cv,
entity_registry as er,
)
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to add a entity to an area."""
domain = DOMAIN
service = "add_entity_to_area"
schema = {
vol.Required("area_id"): cv.string,
vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
area_registry = ar.async_get(self.hass)
if not area_registry.async_get_area(call.data["area_id"]):
msg = f"Area {call.data['area_id']} not found"
raise HomeAssistantError(msg)
entity_registry = er.async_get(self.hass)
for entity_id in call.data["entity_id"]:
entity_registry.async_update_entity(
entity_id,
area_id=call.data["area_id"],
)
@@ -0,0 +1,46 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import (
area_registry as ar,
config_validation as cv,
label_registry as lr,
)
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to add a label to an area."""
domain = DOMAIN
service = "add_label_to_area"
schema = {
vol.Required("label_id"): vol.All(cv.ensure_list, [cv.string]),
vol.Required("area_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
label_registry = lr.async_get(self.hass)
for label_id in call.data["label_id"]:
if not label_registry.async_get_label(label_id):
msg = f"Label {label_id} not found"
raise HomeAssistantError(msg)
area_registry = ar.async_get(self.hass)
for area_id in call.data["area_id"]:
if area_entry := area_registry.async_get_area(area_id):
labels = area_entry.labels.copy()
labels.update(call.data["label_id"])
area_registry.async_update(area_id, labels=labels)
@@ -0,0 +1,46 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import (
config_validation as cv,
device_registry as dr,
label_registry as lr,
)
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to add a label to a device."""
domain = DOMAIN
service = "add_label_to_device"
schema = {
vol.Required("label_id"): vol.All(cv.ensure_list, [cv.string]),
vol.Required("device_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
label_registry = lr.async_get(self.hass)
for label_id in call.data["label_id"]:
if not label_registry.async_get_label(label_id):
msg = f"Label {label_id} not found"
raise HomeAssistantError(msg)
device_registry = dr.async_get(self.hass)
for device_id in call.data["device_id"]:
if device_entry := device_registry.async_get(device_id):
labels = device_entry.labels.copy()
labels.update(call.data["label_id"])
device_registry.async_update_device(device_id, labels=labels)
@@ -0,0 +1,46 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import (
config_validation as cv,
entity_registry as er,
label_registry as lr,
)
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to add a label to an entity."""
domain = DOMAIN
service = "add_label_to_entity"
schema = {
vol.Required("label_id"): vol.All(cv.ensure_list, [cv.string]),
vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
label_registry = lr.async_get(self.hass)
for label_id in call.data["label_id"]:
if not label_registry.async_get_label(label_id):
msg = f"Label {label_id} not found"
raise HomeAssistantError(msg)
entity_registry = er.async_get(self.hass)
for entity_id in call.data["entity_id"]:
if entity_entry := entity_registry.async_get(entity_id):
labels = entity_entry.labels.copy()
labels.update(call.data["label_id"])
entity_registry.async_update_entity(entity_id, labels=labels)
@@ -0,0 +1,36 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import area_registry as ar, config_validation as cv
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant area service to create areas on the fly."""
domain = DOMAIN
service = "create_area"
schema = {
vol.Required("name"): cv.string,
vol.Optional("aliases"): [cv.string],
vol.Optional("icon"): cv.icon,
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
area_registry = ar.async_get(self.hass)
area_registry.async_create(
name=call.data["name"],
aliases=call.data.get("aliases"),
icon=call.data.get("icon"),
)
@@ -0,0 +1,38 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, floor_registry as fr
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant floor service to create floors on the fly."""
domain = DOMAIN
service = "create_floor"
schema = {
vol.Required("name"): cv.string,
vol.Optional("aliases"): [cv.string],
vol.Optional("icon"): cv.icon,
vol.Optional("level"): vol.Coerce(int),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
floor_registry = fr.async_get(self.hass)
floor_registry.async_create(
name=call.data["name"],
aliases=call.data.get("aliases"),
icon=call.data.get("icon"),
level=call.data.get("level"),
)
@@ -0,0 +1,69 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, label_registry as lr
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
SUPPORTED_LABEL_THEME_COLORS = {
"primary",
"accent",
"disabled",
"amber",
"black",
"blue-grey",
"blue",
"brown",
"cyan",
"dark-grey",
"deep-orange",
"deep-purple",
"green",
"grey",
"indigo",
"light-blue",
"light-green",
"light-grey",
"lime",
"orange",
"pink",
"purple",
"red",
"teal",
"white",
"yellow",
}
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to create labels on the fly."""
domain = DOMAIN
service = "create_label"
schema = {
vol.Required("name"): cv.string,
vol.Optional("color"): vol.Any(
cv.color_hex, vol.In(SUPPORTED_LABEL_THEME_COLORS)
),
vol.Optional("description"): cv.string,
vol.Optional("icon"): cv.icon,
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
label_registry = lr.async_get(self.hass)
label_registry.async_create(
name=call.data["name"],
color=call.data.get("color"),
description=call.data.get("description"),
icon=call.data.get("icon"),
)
@@ -0,0 +1,30 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.const import ATTR_RESTORED
from homeassistant.helpers import entity_registry as er
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to delete all orphaned entities."""
domain = DOMAIN
service = "delete_all_orphaned_entities"
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
entity_registry = er.async_get(self.hass)
for state in self.hass.states.async_all():
if not state.attributes.get(ATTR_RESTORED):
continue
entity_registry.async_remove(state.entity_id)
self.hass.states.async_remove(state.entity_id, call.context)
@@ -0,0 +1,28 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import area_registry as ar, config_validation as cv
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant area service to delete areas on the fly."""
domain = DOMAIN
service = "delete_area"
schema = {vol.Required("area_id"): cv.string}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
area_registry = ar.async_get(self.hass)
area_registry.async_delete(call.data["area_id"])
@@ -0,0 +1,28 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, floor_registry as fr
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant floor service to delete floors on the fly."""
domain = DOMAIN
service = "delete_floor"
schema = {vol.Required("floor_id"): cv.string}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
floor_registry = fr.async_get(self.hass)
floor_registry.async_delete(call.data["floor_id"])
@@ -0,0 +1,28 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, label_registry as lr
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to delete labels on the fly."""
domain = DOMAIN
service = "delete_label"
schema = {vol.Required("label_id"): cv.string}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
label_registry = lr.async_get(self.hass)
label_registry.async_delete(call.data["label_id"])
@@ -0,0 +1,32 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.config_entries import ConfigEntryDisabler
from homeassistant.helpers import config_validation as cv
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to disable a config entry."""
domain = DOMAIN
service = "disable_config_entry"
schema = {vol.Required("config_entry_id"): vol.All(cv.ensure_list, [cv.string])}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
for config_entry_id in call.data["config_entry_id"]:
await self.hass.config_entries.async_set_disabled_by(
config_entry_id,
disabled_by=ConfigEntryDisabler.USER,
)
@@ -0,0 +1,30 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, device_registry as dr
from ....services import AbstractSpookAdminService
from ..device import async_disable_device_and_parent_if_needed
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to disable a device."""
domain = DOMAIN
service = "disable_device"
schema = {vol.Required("device_id"): vol.All(cv.ensure_list, [cv.string])}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
device_registry = dr.async_get(self.hass)
for device_id in call.data["device_id"]:
async_disable_device_and_parent_if_needed(device_registry, device_id)
@@ -0,0 +1,32 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, entity_registry as er
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to disable an entity."""
domain = DOMAIN
service = "disable_entity"
schema = {vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string])}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
entity_registry = er.async_get(self.hass)
for entity_id in call.data["entity_id"]:
entity_registry.async_update_entity(
entity_id=entity_id,
disabled_by=er.RegistryEntryDisabler.USER,
)
@@ -0,0 +1,39 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to disable polling."""
domain = DOMAIN
service = "disable_polling"
schema = {vol.Required("config_entry_id"): cv.string}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
if not (
entry := self.hass.config_entries.async_get_entry(
call.data["config_entry_id"],
)
):
msg = f"Config entry not found: {call.data['config_entry_id']}"
raise HomeAssistantError(msg)
self.hass.config_entries.async_update_entry(
entry,
pref_disable_polling=True,
)
@@ -0,0 +1,36 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to disable a user."""
domain = DOMAIN
service = "disable_user"
schema = {vol.Required("user_id"): vol.All(cv.ensure_list, [cv.string])}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
for user_id in call.data["user_id"]:
user = await self.hass.auth.async_get_user(user_id)
if user is None:
message = f"Could not find user: {user_id}"
raise HomeAssistantError(message)
if user.system_generated:
message = f"Cannot disable a system-generated user: {user_id}"
raise HomeAssistantError(message)
await self.hass.auth.async_update_user(user, is_active=False)
@@ -0,0 +1,31 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to enable a config entry."""
domain = DOMAIN
service = "enable_config_entry"
schema = {vol.Required("config_entry_id"): vol.All(cv.ensure_list, [cv.string])}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
for config_entry_id in call.data["config_entry_id"]:
await self.hass.config_entries.async_set_disabled_by(
config_entry_id,
disabled_by=None,
)
@@ -0,0 +1,30 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, device_registry as dr
from ....services import AbstractSpookAdminService
from ..device import async_enable_device_and_parent
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to enable a device."""
domain = DOMAIN
service = "enable_device"
schema = {vol.Required("device_id"): vol.All(cv.ensure_list, [cv.string])}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
device_registry = dr.async_get(self.hass)
for device_id in call.data["device_id"]:
async_enable_device_and_parent(device_registry, device_id)
@@ -0,0 +1,32 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, entity_registry as er
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to enable an entity."""
domain = DOMAIN
service = "enable_entity"
schema = {vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string])}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
entity_registry = er.async_get(self.hass)
for entity_id in call.data["entity_id"]:
entity_registry.async_update_entity(
entity_id=entity_id,
disabled_by=None,
)
@@ -0,0 +1,39 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to enable polling."""
domain = DOMAIN
service = "enable_polling"
schema = {vol.Required("config_entry_id"): cv.string}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
if not (
entry := self.hass.config_entries.async_get_entry(
call.data["config_entry_id"],
)
):
msg = f"Config entry not found: {call.data['config_entry_id']}"
raise HomeAssistantError(msg)
self.hass.config_entries.async_update_entry(
entry,
pref_disable_polling=False,
)
@@ -0,0 +1,36 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to enable a user."""
domain = DOMAIN
service = "enable_user"
schema = {vol.Required("user_id"): vol.All(cv.ensure_list, [cv.string])}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
for user_id in call.data["user_id"]:
user = await self.hass.auth.async_get_user(user_id)
if user is None:
message = f"Could not find user: {user_id}"
raise HomeAssistantError(message)
if user.system_generated:
message = f"Cannot enable a system-generated user: {user_id}"
raise HomeAssistantError(message)
await self.hass.auth.async_update_user(user, is_active=True)
@@ -0,0 +1,33 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, entity_registry as er
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to hide an entity."""
domain = DOMAIN
service = "hide_entity"
admin = True
schema = {vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string])}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
entity_registry = er.async_get(self.hass)
for entity_id in call.data["entity_id"]:
entity_registry.async_update_entity(
entity_id=entity_id,
hidden_by=er.RegistryEntryHider.USER,
)
@@ -0,0 +1,76 @@
"""Spook - Your homie."""
from __future__ import annotations
import asyncio
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.config_entries import DISCOVERY_SOURCES, SOURCE_IGNORE
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.translation import async_get_translations
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to ignore all discovered devices."""
domain = DOMAIN
service = "ignore_all_discovered"
schema = {vol.Optional("domain"): vol.All(cv.ensure_list, [cv.string])}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
flows_to_ignore = [
flow
for flow in self.hass.config_entries.flow.async_progress()
if (
"context" in flow
and "source" in flow["context"]
and flow["context"]["source"] in DISCOVERY_SOURCES
and (
"domain" not in call.data or flow["handler"] in call.data["domain"]
)
)
]
translations = await async_get_translations(
self.hass,
"en",
"config_flow",
integrations=(flow["handler"] for flow in flows_to_ignore),
config_flow=True,
)
tasks = []
for flow in flows_to_ignore:
title = "Ignored by Spook"
if flow_title := translations.get(
f"component.{flow['handler']}.config.flow_title",
):
title = flow_title.format(**flow["context"]["title_placeholders"])
elif (
"title_placeholders" in flow["context"]
and "name" in flow["context"]["title_placeholders"]
):
title = flow["context"]["title_placeholders"]["name"]
tasks.append(
self.hass.config_entries.flow.async_init(
flow["handler"],
context={"source": SOURCE_IGNORE},
data={
"unique_id": flow["context"]["unique_id"],
"title": f"{title} 👻",
},
),
)
if tasks:
await asyncio.gather(*tasks)
@@ -0,0 +1,47 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
from sqlalchemy import create_engine, text
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.components.recorder import (
get_instance,
)
from homeassistant.core import ServiceResponse, SupportsResponse
from ....services import AbstractSpookService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookService):
"""Home Assistant Core integration service to list all orphaned database entities."""
domain = DOMAIN
service = "list_orphaned_database_entities"
supports_response = SupportsResponse.ONLY
async def async_handle_service(self, call: ServiceCall) -> ServiceResponse:
"""Handle the service call."""
query = text(
"""
SELECT DISTINCT(entity_id) FROM states_meta
"""
)
db_url = get_instance(self.hass).db_url
engine = create_engine(db_url)
with engine.connect() as conn:
response = conn.execute(query)
db_list = [e[0] for e in response]
states_list = self.hass.states.async_entity_ids()
compared_list = set(db_list).difference(states_list)
if call.return_response:
return {
"count": len(compared_list),
"entities": list(compared_list),
}
return None
@@ -0,0 +1,40 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import area_registry as ar, config_validation as cv
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to remove an alias to an area."""
domain = DOMAIN
service = "remove_alias_from_area"
schema = {
vol.Required("area_id"): cv.string,
vol.Required("alias"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
area_registry = ar.async_get(self.hass)
if not (area := area_registry.async_get_area(call.data["area_id"])):
msg = f"Area {call.data['area_id']} not found"
raise HomeAssistantError(msg)
aliases = area.aliases.copy()
area_registry.async_update(
call.data["area_id"],
aliases=aliases.difference(call.data["alias"]),
)
@@ -0,0 +1,40 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv, floor_registry as fr
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to remove an alias from a floor."""
domain = DOMAIN
service = "remove_alias_from_floor"
schema = {
vol.Required("floor_id"): cv.string,
vol.Required("alias"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
floor_registry = fr.async_get(self.hass)
if not (floor := floor_registry.async_get_floor(call.data["floor_id"])):
msg = f"Floor {call.data['floor_id']} not found"
raise HomeAssistantError(msg)
aliases = floor.aliases.copy()
floor_registry.async_update(
call.data["floor_id"],
aliases=aliases.difference(call.data["alias"]),
)
@@ -0,0 +1,34 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import area_registry as ar, config_validation as cv
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to remove an area from a floor."""
domain = DOMAIN
service = "remove_area_from_floor"
schema = {
vol.Required("area_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
area_registry = ar.async_get(self.hass)
for area_id in call.data["area_id"]:
area_registry.async_update(
area_id,
floor_id=None,
)
@@ -0,0 +1,34 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, device_registry as dr
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to remove a device from an area."""
domain = DOMAIN
service = "remove_device_from_area"
schema = {
vol.Required("device_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
device_registry = dr.async_get(self.hass)
for device_id in call.data["device_id"]:
device_registry.async_update_device(
device_id,
area_id=None,
)
@@ -0,0 +1,34 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, entity_registry as er
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to remove an entity from an area."""
domain = DOMAIN
service = "remove_entity_from_area"
schema = {
vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
entity_registry = er.async_get(self.hass)
for entity_id in call.data["entity_id"]:
entity_registry.async_update_entity(
entity_id,
area_id=None,
)
@@ -0,0 +1,35 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import area_registry as ar, config_validation as cv
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to remove a label from an area."""
domain = DOMAIN
service = "remove_label_from_area"
schema = {
vol.Required("label_id"): vol.All(cv.ensure_list, [cv.string]),
vol.Required("area_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
area_registry = ar.async_get(self.hass)
for area_id in call.data["area_id"]:
if area_entry := area_registry.async_get_area(area_id):
labels = area_entry.labels.copy()
labels.difference_update(call.data["label_id"])
area_registry.async_update(area_id, labels=labels)
@@ -0,0 +1,35 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, device_registry as dr
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to remove a label from a device."""
domain = DOMAIN
service = "remove_label_from_device"
schema = {
vol.Required("label_id"): vol.All(cv.ensure_list, [cv.string]),
vol.Required("device_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
device_registry = dr.async_get(self.hass)
for device_id in call.data["device_id"]:
if device_entry := device_registry.async_get(device_id):
labels = device_entry.labels.copy()
labels.difference_update(call.data["label_id"])
device_registry.async_update_device(device_id, labels=labels)
@@ -0,0 +1,35 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, entity_registry as er
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to remove a label from an entity."""
domain = DOMAIN
service = "remove_label_from_entity"
schema = {
vol.Required("label_id"): vol.All(cv.ensure_list, [cv.string]),
vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
entity_registry = er.async_get(self.hass)
for entity_id in call.data["entity_id"]:
if entity_entry := entity_registry.async_get(entity_id):
labels = entity_entry.labels.copy()
labels.difference_update(call.data["label_id"])
entity_registry.async_update_entity(entity_id, labels=labels)
@@ -0,0 +1,35 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, entity_registry as er
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to rename an entity."""
domain = DOMAIN
service = "rename_entity"
schema = {
vol.Required("name"): cv.string,
vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
entity_registry = er.async_get(self.hass)
for entity_id in call.data["entity_id"]:
entity_registry.async_update_entity(
entity_id=entity_id,
name=call.data["name"],
)
@@ -0,0 +1,50 @@
"""Spook - Your homie."""
from __future__ import annotations
import asyncio
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.const import RESTART_EXIT_CODE
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from ....const import LOGGER
from ....services import AbstractSpookAdminService, ReplaceExistingService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService, ReplaceExistingService):
"""Home Assistant service to restart Home Assistant.
It overrides the built-in restart service to add a force option.
"""
domain = DOMAIN
service = "restart"
schema = {
vol.Optional("safe_mode", default=False): cv.boolean,
vol.Optional("force", default=False): cv.boolean,
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
if call.data["force"]:
LOGGER.warning("!! Forcing an Home Assistant restart !!")
self.hass.data["homeassistant_stop"] = asyncio.create_task(
self.hass.async_stop(RESTART_EXIT_CODE),
)
return
if not self.overriden_service:
msg = "Spook encountered an error while restarting Home Assistant."
raise HomeAssistantError(
msg,
)
self.hass.async_run_hass_job(self.overriden_service.job, call)
@@ -0,0 +1,39 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import area_registry as ar, config_validation as cv
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to set the aliases of an area."""
domain = DOMAIN
service = "set_area_aliases"
schema = {
vol.Required("area_id"): cv.string,
vol.Required("aliases"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
area_registry = ar.async_get(self.hass)
if not area_registry.async_get_area(call.data["area_id"]):
msg = f"Area {call.data['area_id']} not found"
raise HomeAssistantError(msg)
area_registry.async_update(
call.data["area_id"],
aliases=set(call.data["aliases"]),
)
@@ -0,0 +1,39 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv, floor_registry as fr
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to set the aliases of a floor."""
domain = DOMAIN
service = "set_floor_aliases"
schema = {
vol.Required("floor_id"): cv.string,
vol.Required("aliases"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
floor_registry = fr.async_get(self.hass)
if not floor_registry.async_get_floor(call.data["floor_id"]):
msg = f"Floor {call.data['floor_id']} not found"
raise HomeAssistantError(msg)
floor_registry.async_update(
call.data["floor_id"],
aliases=set(call.data["aliases"]),
)
@@ -0,0 +1,32 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, entity_registry as er
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to unhide an entity."""
domain = DOMAIN
service = "unhide_entity"
schema = {vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string])}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
entity_registry = er.async_get(self.hass)
for entity_id in call.data["entity_id"]:
entity_registry.async_update_entity(
entity_id=entity_id,
hidden_by=None,
)
@@ -0,0 +1,34 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, entity_registry as er
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to update an entity's ID."""
domain = DOMAIN
service = "update_entity_id"
schema = {
vol.Required("entity_id"): cv.entity_id,
vol.Required("new_entity_id"): cv.entity_id,
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
entity_registry = er.async_get(self.hass)
entity_registry.async_update_entity(
entity_id=call.data["entity_id"],
new_entity_id=call.data["new_entity_id"],
)