From 5b1fd8f58b0579a6064f32b9cf86a75bf1e847e3 Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Sun, 7 Sep 2025 19:51:16 +0200 Subject: [PATCH 1/3] Fix sentence-casing in `volvooncall` (#151863) --- homeassistant/components/volvooncall/strings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/volvooncall/strings.json b/homeassistant/components/volvooncall/strings.json index 44b821b4b017c..8524293d6063f 100644 --- a/homeassistant/components/volvooncall/strings.json +++ b/homeassistant/components/volvooncall/strings.json @@ -6,8 +6,8 @@ "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]", "region": "Region", - "unit_system": "Unit System", - "mutable": "Allow Remote Start / Lock / etc." + "unit_system": "Unit system", + "mutable": "Allow remote start/lock etc." } } }, From 38ea5c681350602e0945eebba15dfaeb1a9bd99e Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Sun, 7 Sep 2025 19:52:05 +0200 Subject: [PATCH 2/3] Bump aioecowitt to 2025.9.1 (#151859) --- homeassistant/components/ecowitt/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/ecowitt/manifest.json b/homeassistant/components/ecowitt/manifest.json index 0d18933f8775a..ba3d01ef6af3f 100644 --- a/homeassistant/components/ecowitt/manifest.json +++ b/homeassistant/components/ecowitt/manifest.json @@ -6,5 +6,5 @@ "dependencies": ["webhook"], "documentation": "https://www.home-assistant.io/integrations/ecowitt", "iot_class": "local_push", - "requirements": ["aioecowitt==2025.9.0"] + "requirements": ["aioecowitt==2025.9.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 0c8a8cfa6c8ba..ca72e573a3403 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -238,7 +238,7 @@ aioeafm==0.1.2 aioeagle==1.1.0 # homeassistant.components.ecowitt -aioecowitt==2025.9.0 +aioecowitt==2025.9.1 # homeassistant.components.co2signal aioelectricitymaps==1.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 94a7b87c28690..3fa2c91299e0b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -226,7 +226,7 @@ aioeafm==0.1.2 aioeagle==1.1.0 # homeassistant.components.ecowitt -aioecowitt==2025.9.0 +aioecowitt==2025.9.1 # homeassistant.components.co2signal aioelectricitymaps==1.1.1 From 65603a382903c2122c043db31844a4438df11085 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Sun, 7 Sep 2025 23:58:51 +0300 Subject: [PATCH 3/3] Add signature to ai_task generated images URL (#151882) --- homeassistant/components/ai_task/__init__.py | 1 - homeassistant/components/ai_task/media_source.py | 13 +++++++++++-- homeassistant/components/ai_task/task.py | 9 +++++++-- tests/components/ai_task/test_media_source.py | 2 +- tests/components/ai_task/test_task.py | 8 +++----- 5 files changed, 22 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/ai_task/__init__.py b/homeassistant/components/ai_task/__init__.py index adae039ea5c2d..1e317186ee493 100644 --- a/homeassistant/components/ai_task/__init__.py +++ b/homeassistant/components/ai_task/__init__.py @@ -211,7 +211,6 @@ class ImageView(HomeAssistantView): url = f"/api/{DOMAIN}/images/{{filename}}" name = f"api:{DOMAIN}/images" - requires_auth = False async def get( self, diff --git a/homeassistant/components/ai_task/media_source.py b/homeassistant/components/ai_task/media_source.py index 08d3a29e95fca..17995584fd748 100644 --- a/homeassistant/components/ai_task/media_source.py +++ b/homeassistant/components/ai_task/media_source.py @@ -2,8 +2,10 @@ from __future__ import annotations +from datetime import timedelta import logging +from homeassistant.components.http.auth import async_sign_path from homeassistant.components.media_player import BrowseError, MediaClass from homeassistant.components.media_source import ( BrowseMediaSource, @@ -14,7 +16,7 @@ ) from homeassistant.core import HomeAssistant -from .const import DATA_IMAGES, DOMAIN +from .const import DATA_IMAGES, DOMAIN, IMAGE_EXPIRY_TIME _LOGGER = logging.getLogger(__name__) @@ -43,7 +45,14 @@ async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia: if image is None: raise Unresolvable(f"Could not resolve media item: {item.identifier}") - return PlayMedia(f"/api/{DOMAIN}/images/{item.identifier}", image.mime_type) + return PlayMedia( + async_sign_path( + self.hass, + f"/api/{DOMAIN}/images/{item.identifier}", + timedelta(seconds=IMAGE_EXPIRY_TIME or 1800), + ), + image.mime_type, + ) async def async_browse_media( self, diff --git a/homeassistant/components/ai_task/task.py b/homeassistant/components/ai_task/task.py index 4efe38425a898..cc333cc7b621c 100644 --- a/homeassistant/components/ai_task/task.py +++ b/homeassistant/components/ai_task/task.py @@ -3,7 +3,7 @@ from __future__ import annotations from dataclasses import dataclass -from datetime import datetime +from datetime import datetime, timedelta from functools import partial import mimetypes from pathlib import Path @@ -13,6 +13,7 @@ import voluptuous as vol from homeassistant.components import camera, conversation, media_source +from homeassistant.components.http.auth import async_sign_path from homeassistant.core import HomeAssistant, ServiceResponse, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.chat_session import ChatSession, async_get_chat_session @@ -239,7 +240,11 @@ def _purge_image(filename: str, now: datetime) -> None: if IMAGE_EXPIRY_TIME > 0: async_call_later(hass, IMAGE_EXPIRY_TIME, partial(_purge_image, filename)) - service_result["url"] = get_url(hass) + f"/api/{DOMAIN}/images/{filename}" + service_result["url"] = get_url(hass) + async_sign_path( + hass, + f"/api/{DOMAIN}/images/{filename}", + timedelta(seconds=IMAGE_EXPIRY_TIME or 1800), + ) service_result["media_source_id"] = f"media-source://{DOMAIN}/images/{filename}" return service_result diff --git a/tests/components/ai_task/test_media_source.py b/tests/components/ai_task/test_media_source.py index 718d729920769..eae597efb9126 100644 --- a/tests/components/ai_task/test_media_source.py +++ b/tests/components/ai_task/test_media_source.py @@ -51,7 +51,7 @@ async def test_resolving( hass, f"media-source://ai_task/{image_id}", None ) assert item is not None - assert item.url == f"/api/ai_task/images/{image_id}" + assert item.url.startswith(f"/api/ai_task/images/{image_id}?authSig=") assert item.mime_type == "image/png" invalid_id = "aabbccddeeff" diff --git a/tests/components/ai_task/test_task.py b/tests/components/ai_task/test_task.py index 2bebf7b60bb0f..288f907ee6dff 100644 --- a/tests/components/ai_task/test_task.py +++ b/tests/components/ai_task/test_task.py @@ -237,9 +237,7 @@ async def test_generate_data_mixed_attachments( hass, dt_util.utcnow() + chat_session.CONVERSATION_TIMEOUT + timedelta(seconds=1), ) - await hass.async_block_till_done() # Need several iterations - await hass.async_block_till_done() # because one iteration of the loop - await hass.async_block_till_done() # simply schedules the cleanup + await hass.async_block_till_done(wait_background_tasks=True) # Verify the temporary file cleaned up assert not camera_attachment.path.exists() @@ -281,7 +279,7 @@ async def test_generate_image( assert result["media_source_id"].startswith("media-source://ai_task/images/") assert result["media_source_id"].endswith("_test_task.png") assert result["url"].startswith("http://10.10.10.10:8123/api/ai_task/images/") - assert result["url"].endswith("_test_task.png") + assert result["url"].count("_test_task.png?authSig=") == 1 assert result["mime_type"] == "image/png" assert result["model"] == "mock_model" assert result["revised_prompt"] == "mock_revised_prompt" @@ -333,7 +331,7 @@ async def test_image_cleanup( instructions="Test prompt", ) - assert result["url"].split("/")[-1] in image_storage + assert result["url"].split("?authSig=")[0].split("/")[-1] in image_storage assert len(image_storage) == 20 async_fire_time_changed(hass, dt_util.utcnow() + timedelta(hours=1, seconds=1))