2024-01-29 14:08:11 +00:00
|
|
|
"""Todo platform for the Bring! integration."""
|
2024-03-08 13:51:32 +00:00
|
|
|
|
2024-01-29 14:08:11 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2025-01-28 12:24:44 +00:00
|
|
|
from itertools import chain
|
2024-01-29 14:08:11 +00:00
|
|
|
from typing import TYPE_CHECKING
|
2024-03-04 14:57:37 +00:00
|
|
|
import uuid
|
2024-01-29 14:08:11 +00:00
|
|
|
|
2024-07-26 14:59:28 +00:00
|
|
|
from bring_api import (
|
|
|
|
BringItem,
|
|
|
|
BringItemOperation,
|
2025-02-06 07:30:41 +00:00
|
|
|
BringList,
|
2024-07-26 14:59:28 +00:00
|
|
|
BringNotificationType,
|
|
|
|
BringRequestException,
|
|
|
|
)
|
2024-04-24 17:41:46 +00:00
|
|
|
import voluptuous as vol
|
2024-01-29 14:08:11 +00:00
|
|
|
|
|
|
|
from homeassistant.components.todo import (
|
|
|
|
TodoItem,
|
|
|
|
TodoItemStatus,
|
|
|
|
TodoListEntity,
|
|
|
|
TodoListEntityFeature,
|
|
|
|
)
|
2025-01-31 12:23:44 +00:00
|
|
|
from homeassistant.core import HomeAssistant, callback
|
2024-04-24 17:41:46 +00:00
|
|
|
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
|
|
|
from homeassistant.helpers import config_validation as cv, entity_platform
|
2025-02-10 20:08:03 +00:00
|
|
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
2024-01-29 14:08:11 +00:00
|
|
|
|
2024-04-24 17:41:46 +00:00
|
|
|
from .const import (
|
|
|
|
ATTR_ITEM_NAME,
|
|
|
|
ATTR_NOTIFICATION_TYPE,
|
|
|
|
DOMAIN,
|
|
|
|
SERVICE_PUSH_NOTIFICATION,
|
|
|
|
)
|
2025-02-06 10:35:41 +00:00
|
|
|
from .coordinator import BringConfigEntry, BringData, BringDataUpdateCoordinator
|
2024-09-08 11:45:12 +00:00
|
|
|
from .entity import BringBaseEntity
|
2024-01-29 14:08:11 +00:00
|
|
|
|
2024-12-06 19:22:48 +00:00
|
|
|
PARALLEL_UPDATES = 0
|
|
|
|
|
2024-01-29 14:08:11 +00:00
|
|
|
|
|
|
|
async def async_setup_entry(
|
|
|
|
hass: HomeAssistant,
|
2024-05-04 23:57:00 +00:00
|
|
|
config_entry: BringConfigEntry,
|
2025-02-10 20:08:03 +00:00
|
|
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
2024-01-29 14:08:11 +00:00
|
|
|
) -> None:
|
|
|
|
"""Set up the sensor from a config entry created in the integrations UI."""
|
2024-05-04 23:57:00 +00:00
|
|
|
coordinator = config_entry.runtime_data
|
2025-01-31 12:23:44 +00:00
|
|
|
lists_added: set[str] = set()
|
2024-01-29 14:08:11 +00:00
|
|
|
|
2025-01-31 12:23:44 +00:00
|
|
|
@callback
|
|
|
|
def add_entities() -> None:
|
|
|
|
"""Add or remove todo list entities."""
|
|
|
|
nonlocal lists_added
|
|
|
|
|
|
|
|
if new_lists := {lst.listUuid for lst in coordinator.lists} - lists_added:
|
|
|
|
async_add_entities(
|
|
|
|
BringTodoListEntity(coordinator, bring_list)
|
|
|
|
for bring_list in coordinator.lists
|
|
|
|
if bring_list.listUuid in new_lists
|
|
|
|
)
|
|
|
|
lists_added |= new_lists
|
|
|
|
|
|
|
|
coordinator.async_add_listener(add_entities)
|
|
|
|
add_entities()
|
2024-01-29 14:08:11 +00:00
|
|
|
|
2024-04-24 17:41:46 +00:00
|
|
|
platform = entity_platform.async_get_current_platform()
|
|
|
|
|
|
|
|
platform.async_register_entity_service(
|
|
|
|
SERVICE_PUSH_NOTIFICATION,
|
2024-08-19 08:18:09 +00:00
|
|
|
{
|
|
|
|
vol.Required(ATTR_NOTIFICATION_TYPE): vol.All(
|
2025-01-28 12:24:44 +00:00
|
|
|
vol.Upper, vol.Coerce(BringNotificationType)
|
2024-08-19 08:18:09 +00:00
|
|
|
),
|
|
|
|
vol.Optional(ATTR_ITEM_NAME): cv.string,
|
|
|
|
},
|
2024-04-24 17:41:46 +00:00
|
|
|
"async_send_message",
|
|
|
|
)
|
|
|
|
|
2024-01-29 14:08:11 +00:00
|
|
|
|
2024-09-08 11:45:12 +00:00
|
|
|
class BringTodoListEntity(BringBaseEntity, TodoListEntity):
|
2024-01-29 14:08:11 +00:00
|
|
|
"""A To-do List representation of the Bring! Shopping List."""
|
|
|
|
|
2024-02-25 12:59:41 +00:00
|
|
|
_attr_translation_key = "shopping_list"
|
2024-09-08 11:45:12 +00:00
|
|
|
_attr_name = None
|
2024-01-29 14:08:11 +00:00
|
|
|
_attr_supported_features = (
|
|
|
|
TodoListEntityFeature.CREATE_TODO_ITEM
|
|
|
|
| TodoListEntityFeature.UPDATE_TODO_ITEM
|
|
|
|
| TodoListEntityFeature.DELETE_TODO_ITEM
|
|
|
|
| TodoListEntityFeature.SET_DESCRIPTION_ON_ITEM
|
|
|
|
)
|
|
|
|
|
2024-09-24 20:55:48 +00:00
|
|
|
def __init__(
|
2025-01-31 12:23:44 +00:00
|
|
|
self, coordinator: BringDataUpdateCoordinator, bring_list: BringList
|
2024-09-24 20:55:48 +00:00
|
|
|
) -> None:
|
|
|
|
"""Initialize the entity."""
|
|
|
|
super().__init__(coordinator, bring_list)
|
|
|
|
self._attr_unique_id = f"{coordinator.config_entry.unique_id}_{self._list_uuid}"
|
|
|
|
|
2024-01-29 14:08:11 +00:00
|
|
|
@property
|
|
|
|
def todo_items(self) -> list[TodoItem]:
|
|
|
|
"""Return the todo items."""
|
|
|
|
return [
|
2024-02-21 15:27:59 +00:00
|
|
|
*(
|
|
|
|
TodoItem(
|
2025-01-28 12:24:44 +00:00
|
|
|
uid=item.uuid,
|
|
|
|
summary=item.itemId,
|
|
|
|
description=item.specification,
|
2024-02-21 15:27:59 +00:00
|
|
|
status=TodoItemStatus.NEEDS_ACTION,
|
|
|
|
)
|
2025-01-28 12:24:44 +00:00
|
|
|
for item in self.bring_list.content.items.purchase
|
2024-02-21 15:27:59 +00:00
|
|
|
),
|
|
|
|
*(
|
|
|
|
TodoItem(
|
2025-01-28 12:24:44 +00:00
|
|
|
uid=item.uuid,
|
|
|
|
summary=item.itemId,
|
|
|
|
description=item.specification,
|
2024-02-21 15:27:59 +00:00
|
|
|
status=TodoItemStatus.COMPLETED,
|
|
|
|
)
|
2025-01-28 12:24:44 +00:00
|
|
|
for item in self.bring_list.content.items.recently
|
2024-02-21 15:27:59 +00:00
|
|
|
),
|
2024-01-29 14:08:11 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def bring_list(self) -> BringData:
|
|
|
|
"""Return the bring list."""
|
|
|
|
return self.coordinator.data[self._list_uuid]
|
|
|
|
|
|
|
|
async def async_create_todo_item(self, item: TodoItem) -> None:
|
|
|
|
"""Add an item to the To-do list."""
|
|
|
|
try:
|
2024-02-20 07:46:02 +00:00
|
|
|
await self.coordinator.bring.save_item(
|
2025-01-28 12:24:44 +00:00
|
|
|
self._list_uuid,
|
2024-05-28 08:25:39 +00:00
|
|
|
item.summary or "",
|
2024-03-04 14:57:37 +00:00
|
|
|
item.description or "",
|
|
|
|
str(uuid.uuid4()),
|
2024-01-29 14:08:11 +00:00
|
|
|
)
|
|
|
|
except BringRequestException as e:
|
2024-04-13 22:26:37 +00:00
|
|
|
raise HomeAssistantError(
|
|
|
|
translation_domain=DOMAIN,
|
|
|
|
translation_key="todo_save_item_failed",
|
|
|
|
translation_placeholders={"name": item.summary or ""},
|
|
|
|
) from e
|
2024-01-29 14:08:11 +00:00
|
|
|
|
|
|
|
await self.coordinator.async_refresh()
|
|
|
|
|
|
|
|
async def async_update_todo_item(self, item: TodoItem) -> None:
|
|
|
|
"""Update an item to the To-do list.
|
|
|
|
|
|
|
|
Bring has an internal 'recent' list which we want to use instead of a todo list
|
2024-02-24 13:18:54 +00:00
|
|
|
status, therefore completed todo list items are matched to the recent list and
|
|
|
|
pending items to the purchase list.
|
2024-01-29 14:08:11 +00:00
|
|
|
|
|
|
|
This results in following behaviour:
|
|
|
|
|
|
|
|
- Completed items will move to the "completed" section in home assistant todo
|
2024-02-21 15:27:59 +00:00
|
|
|
list and get moved to the recently list in bring
|
2024-03-04 14:57:37 +00:00
|
|
|
- Bring shows some odd behaviour when renaming items. This is because Bring
|
|
|
|
did not have unique identifiers for items in the past and this is still
|
|
|
|
a relic from it. Therefore the name is not to be changed! Should a name
|
|
|
|
be changed anyway, the item will be deleted and a new item will be created
|
|
|
|
instead and no update for this item is performed and on the next cloud pull
|
|
|
|
update, it will get cleared and replaced seamlessly.
|
2024-01-29 14:08:11 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
bring_list = self.bring_list
|
|
|
|
|
2025-01-28 12:24:44 +00:00
|
|
|
current_item = next(
|
|
|
|
(
|
|
|
|
i
|
|
|
|
for i in chain(
|
|
|
|
bring_list.content.items.purchase, bring_list.content.items.recently
|
|
|
|
)
|
|
|
|
if i.uuid == item.uid
|
|
|
|
),
|
2024-02-21 15:27:59 +00:00
|
|
|
None,
|
|
|
|
)
|
|
|
|
|
2024-01-29 14:08:11 +00:00
|
|
|
if TYPE_CHECKING:
|
|
|
|
assert item.uid
|
2024-03-04 14:57:37 +00:00
|
|
|
assert current_item
|
2024-01-29 14:08:11 +00:00
|
|
|
|
2025-01-28 12:24:44 +00:00
|
|
|
if item.summary == current_item.itemId:
|
2024-01-29 14:08:11 +00:00
|
|
|
try:
|
2024-03-04 14:57:37 +00:00
|
|
|
await self.coordinator.bring.batch_update_list(
|
2025-01-28 12:24:44 +00:00
|
|
|
self._list_uuid,
|
2024-03-04 14:57:37 +00:00
|
|
|
BringItem(
|
2024-05-28 08:25:39 +00:00
|
|
|
itemId=item.summary or "",
|
|
|
|
spec=item.description or "",
|
2024-03-04 14:57:37 +00:00
|
|
|
uuid=item.uid,
|
|
|
|
),
|
|
|
|
BringItemOperation.ADD
|
|
|
|
if item.status == TodoItemStatus.NEEDS_ACTION
|
|
|
|
else BringItemOperation.COMPLETE,
|
2024-01-29 14:08:11 +00:00
|
|
|
)
|
|
|
|
except BringRequestException as e:
|
2024-04-13 22:26:37 +00:00
|
|
|
raise HomeAssistantError(
|
|
|
|
translation_domain=DOMAIN,
|
|
|
|
translation_key="todo_update_item_failed",
|
|
|
|
translation_placeholders={"name": item.summary or ""},
|
|
|
|
) from e
|
2024-01-29 14:08:11 +00:00
|
|
|
else:
|
|
|
|
try:
|
2024-03-04 14:57:37 +00:00
|
|
|
await self.coordinator.bring.batch_update_list(
|
2025-01-28 12:24:44 +00:00
|
|
|
self._list_uuid,
|
2024-03-04 14:57:37 +00:00
|
|
|
[
|
|
|
|
BringItem(
|
2025-01-28 12:24:44 +00:00
|
|
|
itemId=current_item.itemId,
|
2024-05-28 08:25:39 +00:00
|
|
|
spec=item.description or "",
|
2024-03-04 14:57:37 +00:00
|
|
|
uuid=item.uid,
|
|
|
|
operation=BringItemOperation.REMOVE,
|
|
|
|
),
|
|
|
|
BringItem(
|
2024-05-28 08:25:39 +00:00
|
|
|
itemId=item.summary or "",
|
|
|
|
spec=item.description or "",
|
2024-03-04 14:57:37 +00:00
|
|
|
uuid=str(uuid.uuid4()),
|
|
|
|
operation=BringItemOperation.ADD
|
|
|
|
if item.status == TodoItemStatus.NEEDS_ACTION
|
|
|
|
else BringItemOperation.COMPLETE,
|
|
|
|
),
|
|
|
|
],
|
2024-01-29 14:08:11 +00:00
|
|
|
)
|
2024-03-04 14:57:37 +00:00
|
|
|
|
2024-01-29 14:08:11 +00:00
|
|
|
except BringRequestException as e:
|
2024-04-13 22:26:37 +00:00
|
|
|
raise HomeAssistantError(
|
|
|
|
translation_domain=DOMAIN,
|
|
|
|
translation_key="todo_rename_item_failed",
|
|
|
|
translation_placeholders={"name": item.summary or ""},
|
|
|
|
) from e
|
2024-01-29 14:08:11 +00:00
|
|
|
|
|
|
|
await self.coordinator.async_refresh()
|
|
|
|
|
|
|
|
async def async_delete_todo_items(self, uids: list[str]) -> None:
|
|
|
|
"""Delete an item from the To-do list."""
|
2024-03-04 14:57:37 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
await self.coordinator.bring.batch_update_list(
|
2025-01-28 12:24:44 +00:00
|
|
|
self._list_uuid,
|
2024-03-04 14:57:37 +00:00
|
|
|
[
|
|
|
|
BringItem(
|
|
|
|
itemId=uid,
|
|
|
|
spec="",
|
|
|
|
uuid=uid,
|
|
|
|
)
|
|
|
|
for uid in uids
|
|
|
|
],
|
|
|
|
BringItemOperation.REMOVE,
|
|
|
|
)
|
|
|
|
except BringRequestException as e:
|
2024-04-13 22:26:37 +00:00
|
|
|
raise HomeAssistantError(
|
|
|
|
translation_domain=DOMAIN,
|
|
|
|
translation_key="todo_delete_item_failed",
|
|
|
|
translation_placeholders={"count": str(len(uids))},
|
|
|
|
) from e
|
2024-01-29 14:08:11 +00:00
|
|
|
|
|
|
|
await self.coordinator.async_refresh()
|
2024-04-24 17:41:46 +00:00
|
|
|
|
|
|
|
async def async_send_message(
|
|
|
|
self,
|
|
|
|
message: BringNotificationType,
|
|
|
|
item: str | None = None,
|
|
|
|
) -> None:
|
|
|
|
"""Send a push notification to members of a shared bring list."""
|
|
|
|
|
|
|
|
try:
|
|
|
|
await self.coordinator.bring.notify(self._list_uuid, message, item or None)
|
|
|
|
except BringRequestException as e:
|
|
|
|
raise HomeAssistantError(
|
|
|
|
translation_domain=DOMAIN,
|
|
|
|
translation_key="notify_request_failed",
|
|
|
|
) from e
|
|
|
|
except ValueError as e:
|
|
|
|
raise ServiceValidationError(
|
|
|
|
translation_domain=DOMAIN,
|
2025-01-12 15:27:31 +00:00
|
|
|
translation_key="notify_missing_argument",
|
|
|
|
translation_placeholders={"field": "item"},
|
2024-04-24 17:41:46 +00:00
|
|
|
) from e
|