From 0a218acd453585a625b0199675025b9dbb72b2e8 Mon Sep 17 00:00:00 2001 From: flan Date: Mon, 3 Aug 2026 20:23:49 +0000 Subject: [PATCH] Report the item a cart action actually acted on MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _cart_action scraped the first .item-name off the item page, which belongs to whatever is in the mini-cart rather than the item being added — so an add announced someone else's groceries in its event payload. Callers pass the name they resolved instead; verified by an add/remove round trip against a live order. --- custom_components/freshharvest/actions.py | 18 +++++++++++------- custom_components/freshharvest/todo.py | 4 ++-- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/custom_components/freshharvest/actions.py b/custom_components/freshharvest/actions.py index 6b56194..ac6ba42 100644 --- a/custom_components/freshharvest/actions.py +++ b/custom_components/freshharvest/actions.py @@ -323,7 +323,7 @@ class FreshHarvestActions: return await self._client.async_fetch(SHOP_ITEM.format(item_id=item_id)) async def async_add_item( - self, item_id: int | str, dry_run: bool = True + self, item_id: int | str, dry_run: bool = True, name: str | None = None ) -> ActionResult: """Add one of an item to the open order. @@ -331,14 +331,16 @@ class FreshHarvestActions: absence *is* the out-of-stock signal — no separate stock lookup can go stale behind our back. """ - return await self._cart_action("add", item_id, dry_run) + return await self._cart_action("add", item_id, dry_run, name) async def async_remove_item( - self, item_id: int | str, dry_run: bool = True + self, item_id: int | str, dry_run: bool = True, name: str | None = None ) -> ActionResult: - return await self._cart_action("remove", item_id, dry_run) + return await self._cart_action("remove", item_id, dry_run, name) - async def _cart_action(self, mode: str, item_id, dry_run: bool) -> ActionResult: + async def _cart_action( + self, mode: str, item_id, dry_run: bool, name: str | None = None + ) -> ActionResult: page = await self._item_page(item_id) m = re.search(r'orderManage\("%s","([^"]+)"' % mode, page) if not m: @@ -346,14 +348,16 @@ class FreshHarvestActions: f"item {item_id} cannot be {mode}ed right now — the page offers " "no control for it, which usually means it is out of stock" ) - name = BeautifulSoup(page, "html.parser").select_one(".item-name") + # Do NOT scrape a name off this page: the first `.item-name` belongs to + # whatever is in the mini-cart, not the item being acted on, so an add + # would announce someone else's groceries. Callers know the real name. url = AJAX_ORDER_MANAGE.format( mode=mode, hash=m.group(1), ts=int(time.time() * 1000) ) result = ActionResult( action=f"{mode}_item", ok=True, - target=name.get_text(" ", strip=True) if name else str(item_id), + target=name or str(item_id), submitted={"url": url}, dry_run=dry_run, detail=f"{mode} item {item_id}", diff --git a/custom_components/freshharvest/todo.py b/custom_components/freshharvest/todo.py index 1196a3b..78db086 100644 --- a/custom_components/freshharvest/todo.py +++ b/custom_components/freshharvest/todo.py @@ -151,7 +151,7 @@ class FreshHarvestBox(FreshHarvestEntity, TodoListEntity): item_id, name = await self._algolia_lookup(query) try: result = await self.coordinator.actions.async_add_item( - item_id, dry_run=False + item_id, dry_run=False, name=name ) except FreshHarvestError as err: self._fire(EVENT_ACTION, "add_item", False, query, str(err)) @@ -166,7 +166,7 @@ class FreshHarvestBox(FreshHarvestEntity, TodoListEntity): item_id, name = await self._algolia_lookup(uid) try: await self.coordinator.actions.async_remove_item( - item_id, dry_run=False + item_id, dry_run=False, name=name ) except FreshHarvestError as err: self._fire(EVENT_ACTION, "remove_item", False, uid, str(err))