diff --git a/README.md b/README.md index 04e0dba..006f82f 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,7 @@ target: entity_id: sensor.album_name_asset_limit data: limit: 10 # Maximum number of assets (1-100) + offset: 0 # Number of assets to skip (for pagination) favorite_only: false # true = favorites only, false = all assets filter_min_rating: 4 # Min rating (1-5) order_by: "date" # Options: "date", "rating", "name", "random" @@ -150,11 +151,16 @@ data: asset_type: "all" # Options: "all", "photo", "video" min_date: "2024-01-01" # Optional: assets created on or after this date max_date: "2024-12-31" # Optional: assets created on or before this date + on_this_day: "2024-02-14" # Optional: filter by month and day (memories) + city: "Paris" # Optional: filter by city name + state: "California" # Optional: filter by state/region + country: "France" # Optional: filter by country ``` **Parameters:** - `limit` (optional, default: 10): Maximum number of assets to return (1-100) +- `offset` (optional, default: 0): Number of assets to skip before returning results. Use with `limit` for pagination (e.g., `offset: 0, limit: 10` for first page, `offset: 10, limit: 10` for second page) - `favorite_only` (optional, default: false): Filter to show only favorite assets - `filter_min_rating` (optional, default: 1): Minimum rating for assets (1-5 stars). Applied independently of `favorite_only` - `order_by` (optional, default: "date"): Field to sort assets by @@ -171,6 +177,10 @@ data: - `"video"`: Return only videos - `min_date` (optional): Filter assets created on or after this date. Use ISO 8601 format (e.g., `"2024-01-01"` or `"2024-01-01T10:30:00"`) - `max_date` (optional): Filter assets created on or before this date. Use ISO 8601 format (e.g., `"2024-12-31"` or `"2024-12-31T23:59:59"`) +- `on_this_day` (optional): Filter assets by matching month and day (memories/anniversary filter). Provide a date in ISO 8601 format (e.g., `"2024-02-14"`) to get all assets taken on February 14th of any year +- `city` (optional): Filter assets by city name (case-insensitive substring match). Based on reverse geocoded location from asset GPS data +- `state` (optional): Filter assets by state/region name (case-insensitive substring match). Based on reverse geocoded location from asset GPS data +- `country` (optional): Filter assets by country name (case-insensitive substring match). Based on reverse geocoded location from asset GPS data **Examples:** @@ -254,6 +264,72 @@ data: order: "descending" ``` +Get "On This Day" memories (photos from today's date in previous years): + +```yaml +service: immich_album_watcher.get_assets +target: + entity_id: sensor.album_name_asset_limit +data: + limit: 20 + on_this_day: "{{ now().strftime('%Y-%m-%d') }}" + order_by: "date" + order: "ascending" +``` + +Paginate through all assets (first page): + +```yaml +service: immich_album_watcher.get_assets +target: + entity_id: sensor.album_name_asset_limit +data: + limit: 10 + offset: 0 + order_by: "date" + order: "descending" +``` + +Paginate through all assets (second page): + +```yaml +service: immich_album_watcher.get_assets +target: + entity_id: sensor.album_name_asset_limit +data: + limit: 10 + offset: 10 + order_by: "date" + order: "descending" +``` + +Get photos taken in a specific city: + +```yaml +service: immich_album_watcher.get_assets +target: + entity_id: sensor.album_name_asset_limit +data: + limit: 50 + city: "Paris" + asset_type: "photo" + order_by: "date" + order: "descending" +``` + +Get all assets from a specific country: + +```yaml +service: immich_album_watcher.get_assets +target: + entity_id: sensor.album_name_asset_limit +data: + limit: 100 + country: "Japan" + order_by: "date" + order: "ascending" +``` + ### Send Telegram Notification Send notifications to Telegram. Supports multiple formats: diff --git a/custom_components/immich_album_watcher/coordinator.py b/custom_components/immich_album_watcher/coordinator.py index 3112010..ede7862 100644 --- a/custom_components/immich_album_watcher/coordinator.py +++ b/custom_components/immich_album_watcher/coordinator.py @@ -435,6 +435,7 @@ class ImmichAlbumWatcherCoordinator(DataUpdateCoordinator[AlbumData | None]): async def async_get_assets( self, limit: int = 10, + offset: int = 0, favorite_only: bool = False, filter_min_rating: int = 1, order_by: str = "date", @@ -442,11 +443,16 @@ class ImmichAlbumWatcherCoordinator(DataUpdateCoordinator[AlbumData | None]): asset_type: str = "all", min_date: str | None = None, max_date: str | None = None, + on_this_day: str | None = None, + city: str | None = None, + state: str | None = None, + country: str | None = None, ) -> list[dict[str, Any]]: """Get assets from the album with optional filtering and ordering. Args: limit: Maximum number of assets to return (1-100) + offset: Number of assets to skip before returning results (for pagination) favorite_only: Filter to show only favorite assets filter_min_rating: Minimum rating for assets (1-5) order_by: Field to sort by - 'date', 'rating', or 'name' @@ -454,6 +460,10 @@ class ImmichAlbumWatcherCoordinator(DataUpdateCoordinator[AlbumData | None]): asset_type: Asset type filter - 'all', 'photo', or 'video' min_date: Filter assets created on or after this date (ISO 8601 format) max_date: Filter assets created on or before this date (ISO 8601 format) + on_this_day: Filter assets by matching month and day (ISO 8601 format) + city: Filter assets by city (case-insensitive substring match) + state: Filter assets by state/region (case-insensitive substring match) + country: Filter assets by country (case-insensitive substring match) Returns: List of asset data dictionaries @@ -484,6 +494,39 @@ class ImmichAlbumWatcherCoordinator(DataUpdateCoordinator[AlbumData | None]): if max_date: assets = [a for a in assets if a.created_at <= max_date] + # Apply "on this day" filtering (match month and day) + if on_this_day: + try: + # Parse the reference date (supports ISO 8601 format) + ref_date = datetime.fromisoformat(on_this_day.replace("Z", "+00:00")) + ref_month = ref_date.month + ref_day = ref_date.day + + def matches_day(asset: AssetInfo) -> bool: + """Check if asset's date matches the reference month and day.""" + try: + asset_date = datetime.fromisoformat( + asset.created_at.replace("Z", "+00:00") + ) + return asset_date.month == ref_month and asset_date.day == ref_day + except (ValueError, AttributeError): + return False + + assets = [a for a in assets if matches_day(a)] + except ValueError: + _LOGGER.warning("Invalid on_this_day date format: %s", on_this_day) + + # Apply geolocation filtering (case-insensitive substring match) + if city: + city_lower = city.lower() + assets = [a for a in assets if a.city and city_lower in a.city.lower()] + if state: + state_lower = state.lower() + assets = [a for a in assets if a.state and state_lower in a.state.lower()] + if country: + country_lower = country.lower() + assets = [a for a in assets if a.country and country_lower in a.country.lower()] + # Apply ordering if order_by == "random": import random @@ -508,8 +551,8 @@ class ImmichAlbumWatcherCoordinator(DataUpdateCoordinator[AlbumData | None]): reverse=(order == "descending") ) - # Limit results - assets = assets[:limit] + # Apply offset and limit for pagination + assets = assets[offset : offset + limit] # Build result with all available asset data (matching event data) result = [] diff --git a/custom_components/immich_album_watcher/manifest.json b/custom_components/immich_album_watcher/manifest.json index ac37591..4e792ee 100644 --- a/custom_components/immich_album_watcher/manifest.json +++ b/custom_components/immich_album_watcher/manifest.json @@ -8,5 +8,5 @@ "iot_class": "cloud_polling", "issue_tracker": "https://github.com/DolgolyovAlexei/haos-hacs-immich-album-watcher/issues", "requirements": [], - "version": "2.6.0" + "version": "2.7.0" } diff --git a/custom_components/immich_album_watcher/sensor.py b/custom_components/immich_album_watcher/sensor.py index d7ef735..263c2c4 100644 --- a/custom_components/immich_album_watcher/sensor.py +++ b/custom_components/immich_album_watcher/sensor.py @@ -98,6 +98,9 @@ async def async_setup_entry( vol.Optional("limit", default=10): vol.All( vol.Coerce(int), vol.Range(min=1, max=100) ), + vol.Optional("offset", default=0): vol.All( + vol.Coerce(int), vol.Range(min=0) + ), vol.Optional("favorite_only", default=False): bool, vol.Optional("filter_min_rating", default=1): vol.All( vol.Coerce(int), vol.Range(min=1, max=5) @@ -111,6 +114,10 @@ async def async_setup_entry( vol.Optional("asset_type", default="all"): vol.In(["all", "photo", "video"]), vol.Optional("min_date"): str, vol.Optional("max_date"): str, + vol.Optional("on_this_day"): str, + vol.Optional("city"): str, + vol.Optional("state"): str, + vol.Optional("country"): str, }, "async_get_assets", supports_response=SupportsResponse.ONLY, @@ -196,6 +203,7 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se async def async_get_assets( self, limit: int = 10, + offset: int = 0, favorite_only: bool = False, filter_min_rating: int = 1, order_by: str = "date", @@ -203,10 +211,15 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se asset_type: str = "all", min_date: str | None = None, max_date: str | None = None, + on_this_day: str | None = None, + city: str | None = None, + state: str | None = None, + country: str | None = None, ) -> ServiceResponse: """Get assets for this album with optional filtering and ordering.""" assets = await self.coordinator.async_get_assets( limit=limit, + offset=offset, favorite_only=favorite_only, filter_min_rating=filter_min_rating, order_by=order_by, @@ -214,6 +227,10 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se asset_type=asset_type, min_date=min_date, max_date=max_date, + on_this_day=on_this_day, + city=city, + state=state, + country=country, ) return {"assets": assets} diff --git a/custom_components/immich_album_watcher/services.yaml b/custom_components/immich_album_watcher/services.yaml index 24fea99..a0c68fe 100644 --- a/custom_components/immich_album_watcher/services.yaml +++ b/custom_components/immich_album_watcher/services.yaml @@ -24,6 +24,15 @@ get_assets: min: 1 max: 100 mode: slider + offset: + name: Offset + description: Number of assets to skip before returning results (for pagination). Use with limit to fetch assets in pages. + required: false + default: 0 + selector: + number: + min: 0 + mode: box favorite_only: name: Favorite Only description: Filter to show only favorite assets. @@ -95,6 +104,30 @@ get_assets: required: false selector: text: + on_this_day: + name: On This Day + description: Filter assets by matching month and day (memories/anniversary filter). Provide a date in ISO 8601 format (e.g., 2024-02-14) to get all assets taken on February 14th of any year. + required: false + selector: + text: + city: + name: City + description: Filter assets by city name (case-insensitive substring match). Based on reverse geocoded location from asset GPS data. + required: false + selector: + text: + state: + name: State + description: Filter assets by state/region name (case-insensitive substring match). Based on reverse geocoded location from asset GPS data. + required: false + selector: + text: + country: + name: Country + description: Filter assets by country name (case-insensitive substring match). Based on reverse geocoded location from asset GPS data. + required: false + selector: + text: send_telegram_notification: name: Send Telegram Notification diff --git a/custom_components/immich_album_watcher/translations/en.json b/custom_components/immich_album_watcher/translations/en.json index 3207364..3f1ed06 100644 --- a/custom_components/immich_album_watcher/translations/en.json +++ b/custom_components/immich_album_watcher/translations/en.json @@ -143,6 +143,10 @@ "name": "Limit", "description": "Maximum number of assets to return (1-100)." }, + "offset": { + "name": "Offset", + "description": "Number of assets to skip (for pagination)." + }, "favorite_only": { "name": "Favorite Only", "description": "Filter to show only favorite assets." @@ -170,6 +174,22 @@ "max_date": { "name": "Maximum Date", "description": "Filter assets created on or before this date (ISO 8601 format)." + }, + "on_this_day": { + "name": "On This Day", + "description": "Filter assets by matching month and day (memories/anniversary filter)." + }, + "city": { + "name": "City", + "description": "Filter assets by city name (case-insensitive)." + }, + "state": { + "name": "State", + "description": "Filter assets by state/region name (case-insensitive)." + }, + "country": { + "name": "Country", + "description": "Filter assets by country name (case-insensitive)." } } }, diff --git a/custom_components/immich_album_watcher/translations/ru.json b/custom_components/immich_album_watcher/translations/ru.json index e8b9ccc..dba56b7 100644 --- a/custom_components/immich_album_watcher/translations/ru.json +++ b/custom_components/immich_album_watcher/translations/ru.json @@ -143,6 +143,10 @@ "name": "Лимит", "description": "Максимальное количество возвращаемых файлов (1-100)." }, + "offset": { + "name": "Смещение", + "description": "Количество файлов для пропуска (для пагинации)." + }, "favorite_only": { "name": "Только избранные", "description": "Фильтр для отображения только избранных файлов." @@ -170,6 +174,22 @@ "max_date": { "name": "Максимальная дата", "description": "Фильтровать файлы, созданные в эту дату или до (формат ISO 8601)." + }, + "on_this_day": { + "name": "В этот день", + "description": "Фильтр по совпадению месяца и дня (воспоминания/годовщины)." + }, + "city": { + "name": "Город", + "description": "Фильтр по названию города (без учёта регистра)." + }, + "state": { + "name": "Регион", + "description": "Фильтр по названию региона/области (без учёта регистра)." + }, + "country": { + "name": "Страна", + "description": "Фильтр по названию страны (без учёта регистра)." } } },