alexei.dolgolyov 71b79cd919
Some checks are pending
Validate / Hassfest (push) Waiting to run
Move quiet hours from hub config to per-call service params
Quiet hours are now specified per send_telegram_notification call via
quiet_hours_start/quiet_hours_end params instead of being a hub-wide
integration option. This allows different automations to use different
quiet hours windows (or none at all).

- Remove quiet_hours_start/end from config options UI and const.py
- Add quiet_hours_start/end as optional HH:MM params on the service
- Remove ignore_quiet_hours param (omit quiet hours params to send immediately)
- Queue stores quiet_hours_end per item; each unique end time gets its
  own async_track_time_change timer for replay
- On startup, items whose quiet hours have passed are sent immediately
- Add async_remove_indices() to NotificationQueue for selective removal
- Timers are cleaned up when no more items need them

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:04:20 +03:00
2026-01-31 04:16:17 +03:00
2026-01-29 20:37:57 +03:00
2026-01-31 04:16:17 +03:00

Immich Album Watcher

Immich

A Home Assistant custom integration that monitors Immich photo/video library albums for changes and exposes them as Home Assistant entities with event-firing capabilities.

Tip: For the best experience, use this integration with the Immich Album Watcher Blueprint to easily create automations for album change notifications.

Features

  • Album Monitoring - Watch selected Immich albums for asset additions and removals
  • Rich Sensor Data - Multiple sensors per album:
    • Album ID (with album name and share URL attributes)
    • Asset Count (total assets with detected people list)
    • Photo Count (number of photos)
    • Video Count (number of videos)
    • Last Updated (last modification timestamp)
    • Created (album creation date)
    • Public URL (public share link)
    • Protected URL (password-protected share link)
    • Protected Password (password for protected link)
  • Camera Entity - Album thumbnail displayed as a camera entity for dashboards
  • Binary Sensor - "New Assets" indicator that turns on when assets are added
  • Face Recognition - Detects and lists people recognized in album photos
  • Event Firing - Fires Home Assistant events when albums change:
    • immich_album_watcher_album_changed - General album changes
    • immich_album_watcher_assets_added - When new assets are added
    • immich_album_watcher_assets_removed - When assets are removed
  • Enhanced Event Data - Events include detailed asset info:
    • Asset type (photo/video)
    • Filename
    • Creation date
    • Asset owner (who uploaded the asset)
    • Asset description/caption
    • Public URL (if album has a shared link)
    • Detected people in the asset
  • Services - Custom service calls:
    • immich_album_watcher.refresh - Force immediate data refresh
    • immich_album_watcher.get_assets - Get assets from an album with filtering and ordering
    • immich_album_watcher.send_telegram_notification - Send text, photo, video, document, or media group to Telegram
  • Share Link Management - Button entities to create and delete share links:
    • Create/delete public (unprotected) share links
    • Create/delete password-protected share links
    • Edit protected link passwords via Text entity
  • Configurable Polling - Adjustable scan interval (10-3600 seconds)
  • Localization - Available in multiple languages:
    • English
    • Russian (Русский)

Installation

  1. Open HACS in Home Assistant
  2. Click on the three dots in the top right corner
  3. Select Custom repositories
  4. Add this repository URL: https://github.com/DolgolyovAlexei/haos-hacs-immich-album-watcher
  5. Select Integration as the category
  6. Click Add
  7. Search for "Immich Album Watcher" in HACS and install it
  8. Restart Home Assistant
  9. Add the integration via SettingsDevices & ServicesAdd Integration

Manual Installation

  1. Download or clone this repository
  2. Copy the custom_components/immich_album_watcher folder to your Home Assistant config/custom_components directory
  3. Restart Home Assistant
  4. Add the integration via SettingsDevices & ServicesAdd Integration

Configuration

Option Description Default
Server URL Your Immich server URL (e.g., https://immich.example.com) Required
API Key Your Immich API key Required
Albums Albums to monitor Required
Scan Interval How often to check for changes (seconds) 60
Telegram Bot Token Bot token for sending media to Telegram (optional) -
Telegram Cache TTL How long to cache uploaded file IDs (hours, 1-168) 48

External Domain Support

The integration supports connecting to a local Immich server while using an external domain for user-facing URLs. This is useful when:

  • Your Home Assistant connects to Immich via local network (e.g., http://192.168.1.100:2283)
  • But you want share links and asset URLs to use your public domain (e.g., https://photos.example.com)

How it works:

  1. Configure "External domain" in Immich: Administration → Settings → Server → External Domain
  2. The integration automatically fetches this setting on startup
  3. All user-facing URLs (share links, asset URLs in events) use the external domain
  4. API calls and file downloads still use the local connection URL for faster performance

Example:

  • Server URL (in integration config): http://192.168.1.100:2283
  • External Domain (in Immich settings): https://photos.example.com
  • Share links in events: https://photos.example.com/share/...
  • Telegram downloads: via http://192.168.1.100:2283 (fast local network)

If no external domain is configured in Immich, all URLs will use the Server URL from the integration configuration.

Entities Created (per album)

Entity Type Name Description
Sensor Album ID Album identifier with album_name, asset_count, share_url, last_updated_at, and created_at attributes
Sensor Asset Count Total number of assets (includes people list in attributes)
Sensor Photo Count Number of photos in the album
Sensor Video Count Number of videos in the album
Sensor Last Updated When the album was last modified
Sensor Created When the album was created
Sensor Public URL Public share link URL (accessible links without password)
Sensor Protected URL Password-protected share link URL (if any exist)
Sensor Protected Password Password for the protected share link (read-only)
Binary Sensor New Assets On when new assets were recently added
Camera Thumbnail Album cover image
Text Protected Password Editable password for the protected share link
Button Create Share Link Creates an unprotected public share link
Button Delete Share Link Deletes the unprotected public share link
Button Create Protected Link Creates a password-protected share link
Button Delete Protected Link Deletes the password-protected share link

Services

Refresh

Force an immediate refresh of all album data:

service: immich_album_watcher.refresh

Get Assets

Get assets from a specific album with optional filtering and ordering (returns response data). Only returns fully processed assets (videos with completed transcoding, photos with generated thumbnails).

service: immich_album_watcher.get_assets
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"
  order: "descending"          # Options: "ascending", "descending"
  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
  memory_date: "2024-02-14"    # Optional: memories filter (excludes same year)
  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
    • "date": Sort by creation date
    • "rating": Sort by rating (assets without rating are placed last)
    • "name": Sort by filename
    • "random": Random order (ignores order)
  • order (optional, default: "descending"): Sort direction
    • "ascending": Ascending order
    • "descending": Descending order
  • asset_type (optional, default: "all"): Filter by asset type
    • "all": No type filtering, return both photos and videos
    • "photo": Return only photos
    • "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")
  • memory_date (optional): Filter assets by matching month and day, excluding the same year (memories filter like Google Photos). Provide a date in ISO 8601 format (e.g., "2024-02-14") to get all assets taken on February 14th from previous years
  • 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:

Get 5 most recent favorite assets:

service: immich_album_watcher.get_assets
target:
  entity_id: sensor.album_name_asset_limit
data:
  limit: 5
  favorite_only: true
  order_by: "date"
  order: "descending"

Get 10 random assets rated 3 stars or higher:

service: immich_album_watcher.get_assets
target:
  entity_id: sensor.album_name_asset_limit
data:
  limit: 10
  filter_min_rating: 3
  order_by: "random"

Get 20 most recent photos only:

service: immich_album_watcher.get_assets
target:
  entity_id: sensor.album_name_asset_limit
data:
  limit: 20
  asset_type: "photo"
  order_by: "date"
  order: "descending"

Get top 10 highest rated favorite videos:

service: immich_album_watcher.get_assets
target:
  entity_id: sensor.album_name_asset_limit
data:
  limit: 10
  favorite_only: true
  asset_type: "video"
  order_by: "rating"
  order: "descending"

Get photos sorted alphabetically by name:

service: immich_album_watcher.get_assets
target:
  entity_id: sensor.album_name_asset_limit
data:
  limit: 20
  asset_type: "photo"
  order_by: "name"
  order: "ascending"

Get photos from a specific date range:

service: immich_album_watcher.get_assets
target:
  entity_id: sensor.album_name_asset_limit
data:
  limit: 50
  asset_type: "photo"
  min_date: "2024-06-01"
  max_date: "2024-06-30"
  order_by: "date"
  order: "descending"

Get "On This Day" memories (photos from today's date in previous years):

service: immich_album_watcher.get_assets
target:
  entity_id: sensor.album_name_asset_limit
data:
  limit: 20
  memory_date: "{{ now().strftime('%Y-%m-%d') }}"
  order_by: "date"
  order: "ascending"

Paginate through all assets (first page):

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):

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:

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:

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:

  • Text message - When assets is empty or not provided
  • Single document - When assets contains one document (default type)
  • Single photo - When assets contains one photo (type: photo)
  • Single video - When assets contains one video (type: video)
  • Media group - When assets contains multiple photos/videos (documents are sent separately)

The service downloads media from Immich and uploads it to Telegram, bypassing any CORS restrictions. Large lists of photos and videos are automatically split into multiple media groups based on the max_group_size parameter (default: 10 items per group). Documents cannot be grouped and are sent individually.

File ID Caching: When media is uploaded to Telegram, the service caches the returned file_id. Subsequent sends of the same media will use the cached file_id instead of re-uploading, significantly improving performance. The cache TTL is configurable in hub options (default: 48 hours, range: 1-168 hours). The cache is persistent across Home Assistant restarts and is shared across all albums in the hub.

Dual Cache System: The integration maintains two separate caches for optimal performance:

  • Asset ID Cache - For Immich assets with extractable asset IDs (UUIDs). The same asset accessed via different URL types (thumbnail, original, video playback, share links) shares the same cache entry.
  • URL Cache - For non-Immich URLs or URLs without extractable asset IDs. Also used when a custom cache_key is provided.

Smart Cache Keys: The service automatically extracts asset IDs from Immich URLs. Supported URL patterns:

  • /api/assets/{asset_id}/original
  • /api/assets/{asset_id}/thumbnail
  • /api/assets/{asset_id}/video/playback
  • /share/{key}/photos/{asset_id}

You can provide a custom cache_key per asset to override this behavior (stored in URL cache).

Examples:

Text message:

service: immich_album_watcher.send_telegram_notification
target:
  entity_id: sensor.album_name_asset_limit
data:
  chat_id: "-1001234567890"
  caption: "Check out the new album!"
  disable_web_page_preview: true

Single document (default):

service: immich_album_watcher.send_telegram_notification
target:
  entity_id: sensor.album_name_asset_limit
data:
  chat_id: "-1001234567890"
  assets:
    - url: "https://immich.example.com/api/assets/xxx/original?key=yyy"
      content_type: "image/heic"  # Optional: explicit MIME type
  caption: "Original file"

Single photo:

service: immich_album_watcher.send_telegram_notification
target:
  entity_id: sensor.album_name_asset_limit
data:
  chat_id: "-1001234567890"
  assets:
    - url: "https://immich.example.com/api/assets/xxx/thumbnail?key=yyy"
      type: photo
  caption: "Beautiful sunset!"

Media group:

service: immich_album_watcher.send_telegram_notification
target:
  entity_id: sensor.album_name_asset_limit
data:
  chat_id: "-1001234567890"
  assets:
    - url: "https://immich.example.com/api/assets/xxx/thumbnail?key=yyy"
      type: photo
    - url: "https://immich.example.com/api/assets/zzz/video/playback?key=yyy"
      type: video
  caption: "New photos from the album!"
  reply_to_message_id: 123

HTML formatting:

service: immich_album_watcher.send_telegram_notification
target:
  entity_id: sensor.album_name_asset_limit
data:
  chat_id: "-1001234567890"
  caption: |
    <b>Album Updated!</b>
    New photos by <i>{{ trigger.event.data.added_assets[0].owner }}</i>
    <a href="https://immich.example.com/album">View Album</a>
  parse_mode: "HTML"  # Default, can be omitted

Non-blocking mode (fire-and-forget):

service: immich_album_watcher.send_telegram_notification
target:
  entity_id: sensor.album_name_asset_limit
data:
  chat_id: "-1001234567890"
  assets:
    - url: "https://immich.example.com/api/assets/xxx/thumbnail?key=yyy"
      type: photo
  caption: "Quick notification"
  wait_for_response: false  # Automation continues immediately

Using custom cache_key (useful when same media has different URLs):

service: immich_album_watcher.send_telegram_notification
target:
  entity_id: sensor.album_name_asset_limit
data:
  chat_id: "-1001234567890"
  assets:
    - url: "https://immich.example.com/api/assets/xxx/thumbnail?key=yyy"
      type: photo
      cache_key: "asset_xxx"  # Custom key for caching instead of URL
  caption: "Photo with custom cache key"
Field Description Required
chat_id Telegram chat ID to send to Yes
assets List of media items with url, optional type (document/photo/video, default: document), optional content_type (MIME type, e.g., image/jpeg), and optional cache_key (custom key for caching). Empty for text message. Photos and videos can be grouped; documents are sent separately. No
bot_token Telegram bot token (uses configured token if not provided) No
caption For media: caption applied to first item. For text: the message text. Supports HTML formatting by default. No
reply_to_message_id Message ID to reply to No
disable_web_page_preview Disable link previews in text messages No
parse_mode How to parse caption/text. Options: HTML, Markdown, MarkdownV2, or empty string for plain text. Default: HTML No
max_group_size Maximum media items per group (2-10). Large lists split into multiple groups. Default: 10 No
chunk_delay Delay in milliseconds between sending multiple groups (0-60000). Useful for rate limiting. Default: 0 No
wait_for_response Wait for Telegram to finish processing. Set to false for fire-and-forget (automation continues immediately). Default: true No
max_asset_data_size Maximum asset size in bytes. Assets exceeding this limit will be skipped. Default: no limit No
send_large_photos_as_documents Handle photos exceeding Telegram limits (10MB or 10000px dimension sum). If true, send as documents. If false, skip oversized photos. Default: false No
chat_action Chat action to display while processing media (typing, upload_photo, upload_video, upload_document). Set to empty string to disable. Default: typing No

The service returns a response with success status and message_id (single message), message_ids (media group), or groups_sent (number of groups when split). When wait_for_response is false, the service returns immediately with {"success": true, "status": "queued"} while processing continues in the background.

Events

The integration fires multiple event types that you can use in your automations:

Available Events

Event Type Description When Fired
immich_album_watcher_album_changed General album change event Fired for any album change
immich_album_watcher_assets_added Assets were added to the album When new photos/videos are added
immich_album_watcher_assets_removed Assets were removed from the album When photos/videos are removed
immich_album_watcher_album_renamed Album name was changed When the album is renamed
immich_album_watcher_album_deleted Album was deleted When the album is deleted from Immich
immich_album_watcher_album_sharing_changed Album sharing status changed When album is shared or unshared

Example Usage

automation:
  - alias: "New photos added to album"
    trigger:
      - platform: event
        event_type: immich_album_watcher_assets_added
    action:
      - service: notify.mobile_app
        data:
          title: "New Photos"
          message: "{{ trigger.event.data.added_limit }} new photos in {{ trigger.event.data.album_name }}"

  - alias: "Album renamed"
    trigger:
      - platform: event
        event_type: immich_album_watcher_album_renamed
    action:
      - service: notify.mobile_app
        data:
          title: "Album Renamed"
          message: "Album '{{ trigger.event.data.old_name }}' renamed to '{{ trigger.event.data.new_name }}'"

  - alias: "Album deleted"
    trigger:
      - platform: event
        event_type: immich_album_watcher_album_deleted
    action:
      - service: notify.mobile_app
        data:
          title: "Album Deleted"
          message: "Album '{{ trigger.event.data.album_name }}' was deleted"

Event Data

Field Description Available In
hub_name Hub name configured in integration All events
album_id Album ID All events
album_name Current album name All events
album_url Public URL to view the album (only present if album has a shared link) All events except album_deleted
change_type Type of change (assets_added, assets_removed, album_renamed, album_sharing_changed, changed) All events except album_deleted
shared Current sharing status of the album All events except album_deleted
added_limit Number of assets added album_changed, assets_added
removed_limit Number of assets removed album_changed, assets_removed
added_assets List of added assets with details (see below) album_changed, assets_added
removed_assets List of removed asset IDs album_changed, assets_removed
people List of all people detected in the album All events except album_deleted
old_name Previous album name album_renamed
new_name New album name album_renamed
old_shared Previous sharing status album_sharing_changed
new_shared New sharing status album_sharing_changed

Added Assets Fields

Each item in the added_assets list contains the following fields:

Field Description
id Unique asset ID
type Type of asset (IMAGE or VIDEO)
filename Original filename of the asset
created_at Date/time when the asset was originally created
owner Display name of the user who owns the asset
owner_id Unique ID of the user who owns the asset
description Description/caption of the asset (from EXIF data)
is_favorite Whether the asset is marked as favorite (true or false)
rating User rating of the asset (1-5 stars, or null if not rated)
latitude GPS latitude coordinate (or null if no geolocation)
longitude GPS longitude coordinate (or null if no geolocation)
city City name from reverse geocoding (or null if unavailable)
state State/region name from reverse geocoding (or null if unavailable)
country Country name from reverse geocoding (or null if unavailable)
url Public URL to view the asset (only present if album has a shared link)
download_url Direct download URL for the original file (if shared link exists)
playback_url Video playback URL (for VIDEO assets only, if shared link exists)
photo_url Photo preview URL (for IMAGE assets only, if shared link exists)
people List of people detected in this specific asset

Note: Assets are only included in events and service responses when they are fully processed by Immich. For videos, this means transcoding must be complete (with encodedVideoPath). For photos, thumbnail generation must be complete (with thumbhash). This ensures that all media URLs are valid and accessible. Unprocessed assets are silently ignored until their processing completes.

Example accessing asset owner in an automation:

automation:
  - alias: "Notify when someone adds photos"
    trigger:
      - platform: event
        event_type: immich_album_watcher_assets_added
    action:
      - service: notify.mobile_app
        data:
          title: "New Photos"
          message: >
            {{ trigger.event.data.added_assets[0].owner }} added
            {{ trigger.event.data.added_limit }} photos to {{ trigger.event.data.album_name }}

Requirements

  • Home Assistant 2024.1.0 or newer
  • Immich server with API access
  • Valid Immich API key with the following permissions:

Required API Permissions

Permission Required Description
album.read Yes Read album data and asset lists
asset.read Yes Read asset details (type, filename, creation date)
user.read Yes Resolve asset owner names
person.read Yes Read face recognition / people data
sharedLink.read Yes Read shared links for public/protected URL sensors
sharedLink.create Optional Create share links via the Button entities
sharedLink.edit Optional Edit shared link passwords via the Text entity
sharedLink.delete Optional Delete share links via the Button entities

Note: Without optional permissions, the corresponding entities will be unavailable or non-functional.

Contributing

Contributions are welcome! Please feel free to submit issues or pull requests.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Description
No description provided
Readme MIT 1.7 MiB
Languages
Python 100%