diff --git a/homeassistant/components/shopping_list/__init__.py b/homeassistant/components/shopping_list/__init__.py index 20d3078228c9e9..db6f3d4d030d87 100644 --- a/homeassistant/components/shopping_list/__init__.py +++ b/homeassistant/components/shopping_list/__init__.py @@ -220,6 +220,15 @@ async def async_add( ) return item + async def remove_by_name( + self, name: str, context: Context | None = None + ) -> dict[str, JsonValueType] | None: + """Remove the first item with matching `name`.""" + item = next((itm for itm in self.items if itm["name"] == name), None) + if item and isinstance(item["id"], str): + await self.async_remove(str(item["id"]), context) + return item + async def async_remove( self, item_id: str, context: Context | None = None ) -> dict[str, JsonValueType] | None: @@ -241,10 +250,11 @@ async def async_remove_items( for item_id in item_ids: _LOGGER.debug( "Removing %s", + item_id, ) if not (item := items_dict.pop(item_id, None)): raise NoMatchingShoppingListItem( - "Item '{item_id}' not found in shopping list" + f"Item '{item_id}' not found in shopping list" ) removed.append(item) self.items = list(items_dict.values()) diff --git a/homeassistant/components/shopping_list/intent.py b/homeassistant/components/shopping_list/intent.py index 84ea3971293688..1bc24cb5f1e187 100644 --- a/homeassistant/components/shopping_list/intent.py +++ b/homeassistant/components/shopping_list/intent.py @@ -9,12 +9,14 @@ from . import DOMAIN, EVENT_SHOPPING_LIST_UPDATED INTENT_ADD_ITEM = "HassShoppingListAddItem" +INTENT_REMOVE_ITEM = "HassShoppingListRemoveItem" INTENT_LAST_ITEMS = "HassShoppingListLastItems" async def async_setup_intents(hass: HomeAssistant) -> None: """Set up the Shopping List intents.""" intent.async_register(hass, AddItemIntent()) + intent.async_register(hass, RemoveItemIntent()) intent.async_register(hass, ListTopItemsIntent()) @@ -37,6 +39,30 @@ async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse return response +class RemoveItemIntent(intent.IntentHandler): + """Handle RemoveItem intents.""" + + intent_type = INTENT_REMOVE_ITEM + description = "Removes an item from the shopping list" + slot_schema = {"item": cv.string} + platforms = {DOMAIN} + + async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse: + """Handle the intent.""" + slots = self.async_validate_slots(intent_obj.slots) + item = slots["item"]["value"] + result = await intent_obj.hass.data[DOMAIN].remove_by_name(item) + + response = intent_obj.create_response() + intent_obj.hass.bus.async_fire(EVENT_SHOPPING_LIST_UPDATED) + + if not result: + response.async_set_speech(f"{item} is not on your shopping list") + else: + response.async_set_speech(f"Removed {item} from your shopping list") + return response + + class ListTopItemsIntent(intent.IntentHandler): """Handle AddItem intents.""" diff --git a/tests/components/shopping_list/test_intent.py b/tests/components/shopping_list/test_intent.py index 07128835b6a254..253e4ba8ab3db6 100644 --- a/tests/components/shopping_list/test_intent.py +++ b/tests/components/shopping_list/test_intent.py @@ -4,6 +4,28 @@ from homeassistant.helpers import intent +async def test_remove_item_intent(hass: HomeAssistant, sl_setup) -> None: + """Test remove item.""" + await intent.async_handle( + hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}} + ) + + response = await intent.async_handle( + hass, "test", "HassShoppingListRemoveItem", {"item": {"value": "beer"}} + ) + + assert response.speech["plain"]["speech"] == "Removed beer from your shopping list" + + +async def test_remove_item_intent_not_found(hass: HomeAssistant, sl_setup) -> None: + """Test remove item.""" + response = await intent.async_handle( + hass, "test", "HassShoppingListRemoveItem", {"item": {"value": "beer"}} + ) + + assert response.speech["plain"]["speech"] == "beer is not on your shopping list" + + async def test_recent_items_intent(hass: HomeAssistant, sl_setup) -> None: """Test recent items.""" await intent.async_handle(