diff --git a/Common/Immich Album Watcher/README.md b/Common/Immich Album Watcher/README.md index fe0e612..9ddd506 100644 --- a/Common/Immich Album Watcher/README.md +++ b/Common/Immich Album Watcher/README.md @@ -199,19 +199,12 @@ Sends scheduled notifications with existing assets from tracked albums. Uses the | `limit` | Maximum number of assets to fetch (1-100) | | `favorite_only` | Only fetch favorite assets | | `asset_type` | Filter by type (all, photo, video) | -| `order_by` | Sort by (random, date, rating, name, memory) | +| `order_by` | Sort by (random, date, rating, name) | | `order` | Sort direction (ascending, descending) | | `filter_min_rating` | Minimum rating filter (1-5, 0 = no filter) | | `min_date` | Assets created on or after this date (YYYY-MM-DD) | | `max_date` | Assets created on or before this date (YYYY-MM-DD) | -#### Memory Mode ("On This Day") - -When `order_by` is set to `memory`, the blueprint fetches photos taken on today's date in previous years - similar to "On This Day" memories in photo apps. This mode automatically: - -- Uses the `on_this_day` parameter with the current date -- Sorts by date in ascending order (oldest first) - ### Scheduled Assets Message Template Variables | Variable | Description | @@ -235,6 +228,46 @@ To target a specific automation, set its Automation ID in config and include it - **Event data**: `{"automation_id": "your-automation-id"}` +## Memory Mode (On This Day) + +Send scheduled notifications with photos taken on today's date in previous years - similar to "On This Day" or "Memories" features in photo apps. This is a separate feature from Scheduled Assets with its own schedule and settings. + +### Memory Mode Options + +| Option | Description | +|--------|-------------| +| `enable_memory_mode` | Enable/disable memory notifications | +| `interval_hours` | How often to send (1-168 hours) | +| `start_hour` | Hour of day for first notification (0-23) | +| `album_mode` | per_album, combined, or random | +| `limit` | Maximum number of assets to fetch (1-100) | +| `favorite_only` | Only fetch favorite assets | +| `asset_type` | Filter by type (all, photo, video) | +| `min_rating` | Minimum rating filter (1-5, 0 = no filter) | + +### Memory Mode Message Template Variables + +| Variable | Description | +|----------|-------------| +| `{album_name}` | Name of the album | +| `{album_url}` | Share URL for the album | +| `{album_created}` | Album creation date (empty in combined mode) | +| `{album_updated}` | Album last update date (empty in combined mode) | +| `{asset_count}` | Number of memory assets fetched | +| `{assets}` | Formatted list of assets (using asset item template) | +| `{common_date}` | Common date if all assets share same date | +| `{common_location}` | Common location if all assets share same location | + +### Testing Memory Mode + +To test without waiting for the scheduled time, fire this event: + +- **Event type**: `immich_album_watcher_test_memory_mode` + +To target a specific automation, set its Automation ID in config and include it in the event data: + +- **Event data**: `{"automation_id": "your-automation-id"}` + ## Author Alexei Dolgolyov (dolgolyov.alexei@gmail.com) diff --git a/Common/Immich Album Watcher/blueprint.yaml b/Common/Immich Album Watcher/blueprint.yaml index 6890f6f..f44e7b5 100644 --- a/Common/Immich Album Watcher/blueprint.yaml +++ b/Common/Immich Album Watcher/blueprint.yaml @@ -567,9 +567,7 @@ blueprint: scheduled_assets_order_by: name: Order By - description: > - How to sort/select assets. - Memory mode fetches photos taken on today's date in previous years ("On This Day"). + description: "How to sort/select assets" default: "random" selector: select: @@ -582,8 +580,6 @@ blueprint: value: "rating" - label: "Name" value: "name" - - label: "Memory (On This Day)" - value: "memory" scheduled_assets_order: name: Order Direction @@ -642,6 +638,119 @@ blueprint: text: multiline: true + # ------------------------------------------------------------------------- + # Memory Mode (On This Day) + # ------------------------------------------------------------------------- + memory_mode_group: + name: "Memory Mode (On This Day)" + description: "Send scheduled notifications with photos from today's date in previous years" + collapsed: true + input: + enable_memory_mode: + name: Enable Memory Mode + description: > + Send scheduled notifications with photos taken on today's date in previous years. + Similar to "On This Day" or "Memories" features in photo apps. + default: false + selector: + boolean: + + memory_mode_interval_hours: + name: Interval (Hours) + description: "How often to send memory notifications (in hours)" + default: 24 + selector: + number: + min: 1 + max: 168 + unit_of_measurement: hours + mode: slider + + memory_mode_start_hour: + name: Start Hour + description: > + Hour of day (0-23) when the first memory notification should be sent. + Example: Start hour 9 with 24h interval = daily at 09:00. + default: 9 + selector: + number: + min: 0 + max: 23 + unit_of_measurement: hour + mode: slider + + memory_mode_album_mode: + name: Album Mode + description: > + How to handle multiple tracked albums. + Per Album: Send separate notification for each album. + Combined: Fetch memories from all albums into one notification. + Random: Pick one random album each time. + default: "combined" + selector: + select: + options: + - label: "Per Album" + value: "per_album" + - label: "Combined" + value: "combined" + - label: "Random Album" + value: "random" + + memory_mode_limit: + name: Asset Limit + description: "Maximum number of memory assets to fetch" + default: 10 + selector: + number: + min: 1 + max: 100 + mode: slider + + memory_mode_favorite_only: + name: Favorites Only + description: "Only fetch memory assets marked as favorites" + default: false + selector: + boolean: + + memory_mode_type: + name: Asset Type + description: "Filter memory assets by type" + default: "all" + selector: + select: + options: + - label: "All" + value: "all" + - label: "Photos Only" + value: "photo" + - label: "Videos Only" + value: "video" + + memory_mode_min_rating: + name: Minimum Rating + description: > + Only fetch memory assets with this rating or higher (1-5 stars). + Set to 0 to include all assets regardless of rating. + default: 0 + selector: + number: + min: 0 + max: 5 + mode: slider + + memory_mode_message: + name: Message Template + description: > + Message template for memory notifications. + Variables: `{album_name}`, `{album_url}`, `{album_created}`, `{album_updated}`, `{asset_count}`, `{assets}`, `{common_date}`, `{common_location}`. + `{album_created}` and `{album_updated}` are empty in combined mode. + default: "📅 On this day:{assets}" + selector: + text: + multiline: true + # ------------------------------------------------------------------------- # Debug # ------------------------------------------------------------------------- @@ -722,6 +831,13 @@ trigger: event_type: immich_album_watcher_test_scheduled_assets id: "scheduled_assets" + # Manual trigger for testing memory mode + # Fire this event from Developer Tools > Events to test memory mode immediately + # Event type: immich_album_watcher_test_memory_mode + - platform: event + event_type: immich_album_watcher_test_memory_mode + id: "memory_mode" + # ============================================================================= # VARIABLES # ============================================================================= @@ -788,6 +904,17 @@ variables: scheduled_assets_max_date: !input scheduled_assets_max_date scheduled_assets_message_template: !input scheduled_assets_message + # Memory Mode Settings + enable_memory_mode: !input enable_memory_mode + memory_mode_interval_hours: !input memory_mode_interval_hours + memory_mode_start_hour: !input memory_mode_start_hour + memory_mode_album_mode: !input memory_mode_album_mode + memory_mode_limit: !input memory_mode_limit + memory_mode_favorite_only: !input memory_mode_favorite_only + memory_mode_type: !input memory_mode_type + memory_mode_min_rating: !input memory_mode_min_rating + memory_mode_message_template: !input memory_mode_message + # Parse chat IDs from notify entity friendly names (format: "Name (123456789)") # and combine with chat IDs from input_text entities telegram_chat_ids: > @@ -1143,20 +1270,54 @@ variables: {{ album_id_entities }} {% endif %} + # --------------------------------------------------------------------------- + # Memory Mode Variables + # --------------------------------------------------------------------------- + # Check if memory mode should run (every N hours starting from start_hour) + # Manual test event (immich_album_watcher_test_memory_mode) bypasses time check + # If event data contains automation_id, only matching automations respond + should_send_memory_mode: > + {% if trigger.id == 'memory_mode' and enable_memory_mode %} + {% set event_automation_id = trigger.event.data.automation_id | default('') %} + {% if event_automation_id | length > 0 %} + {{ automation_id == event_automation_id }} + {% else %} + {{ true }} + {% endif %} + {% elif trigger.id == 'hourly_timer' and enable_memory_mode %} + {% set current_hour = now().hour %} + {% set start = memory_mode_start_hour | int %} + {% set interval = memory_mode_interval_hours | int %} + {{ (current_hour - start) % interval == 0 }} + {% else %} + {{ false }} + {% endif %} + + # Get list of album entities to process based on memory mode album mode + memory_mode_album_entities: > + {% if memory_mode_album_mode == 'random' %} + {{ [album_id_entities | random] if album_id_entities | length > 0 else [] }} + {% else %} + {{ album_id_entities }} + {% endif %} + # ============================================================================= # CONDITIONS # ============================================================================= condition: # Allow through if: - # 1. Hourly timer or periodic summary trigger and should send periodic summary or scheduled assets + # 1. Hourly timer or periodic summary trigger and should send periodic summary, scheduled assets, or memory mode # 2. Scheduled assets trigger (manual test) - # 3. Event trigger and passes all event-based checks + # 3. Memory mode trigger (manual test) + # 4. Event trigger and passes all event-based checks - condition: template value_template: > {% if trigger.id == 'scheduled_assets' %} {{ should_send_scheduled_assets }} + {% elif trigger.id == 'memory_mode' %} + {{ should_send_memory_mode }} {% elif trigger.id in ['hourly_timer', 'periodic_summary'] %} - {{ should_send_periodic_summary or should_send_scheduled_assets }} + {{ should_send_periodic_summary or should_send_scheduled_assets or should_send_memory_mode }} {% else %} {{ is_hub_tracked and is_album_tracked and should_notify }} {% endif %} @@ -1237,16 +1398,12 @@ action: # Build service data dynamically (omit optional params when not set) - variables: get_assets_data: > - {% set is_memory_mode = scheduled_assets_order_by == 'memory' %} {% set data = { 'limit': scheduled_assets_limit | int, 'favorite_only': scheduled_assets_favorite_only, - 'order_by': 'date' if is_memory_mode else scheduled_assets_order_by, - 'order': 'ascending' if is_memory_mode else scheduled_assets_order + 'order_by': scheduled_assets_order_by, + 'order': scheduled_assets_order } %} - {% if is_memory_mode %} - {% set data = dict(data, on_this_day=now().strftime('%Y-%m-%d')) %} - {% endif %} {% if scheduled_assets_type != 'all' %} {% set data = dict(data, asset_type=scheduled_assets_type) %} {% endif %} @@ -1478,16 +1635,12 @@ action: # Build service data dynamically (omit optional params when not set) - variables: combined_get_assets_data: > - {% set is_memory_mode = scheduled_assets_order_by == 'memory' %} {% set data = { 'limit': per_album_limit | int, 'favorite_only': scheduled_assets_favorite_only, - 'order_by': 'date' if is_memory_mode else scheduled_assets_order_by, - 'order': 'ascending' if is_memory_mode else scheduled_assets_order + 'order_by': scheduled_assets_order_by, + 'order': scheduled_assets_order } %} - {% if is_memory_mode %} - {% set data = dict(data, on_this_day=now().strftime('%Y-%m-%d')) %} - {% endif %} {% if scheduled_assets_type != 'all' %} {% set data = dict(data, asset_type=scheduled_assets_type) %} {% endif %} @@ -1712,11 +1865,501 @@ action: chunk_delay: "{{ telegram_media_delay }}" wait_for_response: false + # --------------------------------------------------------------------------- + # MEMORY MODE: Send "On This Day" memories from tracked albums + # --------------------------------------------------------------------------- + - choose: + - conditions: + - condition: template + value_template: "{{ should_send_memory_mode }}" + sequence: + # Process albums based on album_mode (per_album, combined, random) + - choose: + # Per Album Mode: Send separate notification for each album + - conditions: + - condition: template + value_template: "{{ memory_mode_album_mode in ['per_album', 'random'] }}" + sequence: + - repeat: + for_each: "{{ memory_mode_album_entities }}" + sequence: + - variables: + memory_current_album_entity: "{{ repeat.item }}" + memory_current_album_name: > + {% set name_attr = state_attr(memory_current_album_entity, 'album_name') %} + {{ name_attr if name_attr not in [none, '', 'unknown', 'unavailable'] else states(memory_current_album_entity) }} + memory_current_album_url: > + {% set url_attr = state_attr(memory_current_album_entity, 'share_url') %} + {{ url_attr if url_attr not in [none, 'unknown', 'unavailable'] else '' }} + memory_current_album_created: > + {% set created_attr = state_attr(memory_current_album_entity, 'created_at') | default('', true) %} + {% set created_dt = created_attr | as_datetime(none) if created_attr is string and created_attr | length > 0 else none %} + {{ created_dt.strftime(date_format) if created_dt else '' }} + memory_current_album_updated: > + {% set updated_attr = state_attr(memory_current_album_entity, 'last_updated_at') | default('', true) %} + {% set updated_dt = updated_attr | as_datetime(none) if updated_attr is string and updated_attr | length > 0 else none %} + {{ updated_dt.strftime(date_format) if updated_dt else '' }} + + # Build service data with memory_date parameter + - variables: + memory_get_assets_data: > + {% set data = { + 'limit': memory_mode_limit | int, + 'favorite_only': memory_mode_favorite_only, + 'order_by': 'date', + 'order': 'ascending', + 'memory_date': now().strftime('%Y-%m-%d') + } %} + {% if memory_mode_type != 'all' %} + {% set data = dict(data, asset_type=memory_mode_type) %} + {% endif %} + {% if memory_mode_min_rating | int > 0 %} + {% set data = dict(data, filter_min_rating=memory_mode_min_rating | int) %} + {% endif %} + {{ data }} + + # Fetch memory assets from this album + - service: immich_album_watcher.get_assets + response_variable: memory_fetched_response + target: + entity_id: "{{ memory_current_album_entity }}" + data: "{{ memory_get_assets_data }}" + + # Filter out videos without playback URL + - variables: + memory_fetched_assets: > + {% set raw_assets = memory_fetched_response[memory_current_album_entity].assets | default([]) %} + {% set ns = namespace(assets = []) %} + {% for asset in raw_assets %} + {% if asset.type == 'IMAGE' or (asset.type == 'VIDEO' and (asset.playback_url | default('') | length > 0)) %} + {% set ns.assets = ns.assets + [asset] %} + {% endif %} + {% endfor %} + {{ ns.assets }} + memory_fetched_count: "{{ memory_fetched_assets | length }}" + + # Only send notification if we got assets + - choose: + - conditions: + - condition: template + value_template: "{{ memory_fetched_count | int > 0 }}" + sequence: + # Format assets list using asset templates + - variables: + # Compute unique dates from fetched assets + memory_unique_dates: > + {% set ns = namespace(dates = []) %} + {% for asset in memory_fetched_assets %} + {% set raw_date = asset.created_at | default('', true) %} + {% set dt = raw_date | as_datetime(none) if raw_date is string and raw_date | length > 0 else none %} + {% set formatted_date = dt.strftime(date_format) if dt else '' %} + {% if formatted_date | length > 0 and formatted_date not in ns.dates %} + {% set ns.dates = ns.dates + [formatted_date] %} + {% endif %} + {% endfor %} + {{ ns.dates }} + # Compute unique locations from fetched assets + memory_unique_locations: > + {% set ns = namespace(locations = []) %} + {% for asset in memory_fetched_assets %} + {% set city = asset.city | default('', true) %} + {% set state = asset.state | default('', true) %} + {% set country = asset.country | default('', true) %} + {% if city | length > 0 and state | length > 0 and country | length > 0 %} + {% set formatted_location = location_format | replace('{city}', city) | replace('{state}', state) | replace('{country}', country) %} + {% if formatted_location not in ns.locations %} + {% set ns.locations = ns.locations + [formatted_location] %} + {% endif %} + {% endif %} + {% endfor %} + {{ ns.locations }} + # Check if all fetched assets have complete location data + memory_all_assets_have_location: > + {% set ns = namespace(count = 0) %} + {% for asset in memory_fetched_assets %} + {% set city = asset.city | default('', true) %} + {% set state = asset.state | default('', true) %} + {% set country = asset.country | default('', true) %} + {% if city | length > 0 and state | length > 0 and country | length > 0 %} + {% set ns.count = ns.count + 1 %} + {% endif %} + {% endfor %} + {{ ns.count == memory_fetched_assets | length and memory_fetched_assets | length > 0 }} + memory_assets_list: > + {% set ns = namespace(items = '') %} + {% for asset in memory_fetched_assets %} + {% set asset_template = message_asset_video_template if asset.type == 'VIDEO' else message_asset_image_template %} + {% set raw_date = asset.created_at | default('', true) %} + {% set dt = raw_date | as_datetime(none) if raw_date is string and raw_date | length > 0 else none %} + {% set formatted_date = dt.strftime(date_format) if dt else '' %} + {% set created_if_unique = '' if memory_unique_dates | length == 1 else (date_if_unique_template | replace('{date}', formatted_date) if formatted_date | length > 0 else '') %} + {% set is_favorite = favorite_indicator_template if asset.is_favorite | default(false) else '' %} + {% set rating = asset.rating | default('') | string if asset.rating not in [none, ''] else '' %} + {% set city = asset.city | default('', true) %} + {% set state = asset.state | default('', true) %} + {% set country = asset.country | default('', true) %} + {% set has_full_location = city | length > 0 and state | length > 0 and country | length > 0 %} + {% set formatted_location = location_format | replace('{city}', city) | replace('{state}', state) | replace('{country}', country) if has_full_location else '' %} + {% set location_if_unique = '' if (not has_full_location) or (memory_all_assets_have_location and memory_unique_locations | length == 1) else (location_if_unique_template | replace('{location}', formatted_location)) %} + {% set item = asset_template + | replace('{filename}', asset.filename | default('Unknown')) + | replace('{description}', asset.description | default('')) + | replace('{type}', asset.type | default('Unknown')) + | replace('{created}', formatted_date) + | replace('{created_if_unique}', created_if_unique) + | replace('{owner}', asset.owner | default('Unknown')) + | replace('{url}', asset.url | default('')) + | replace('{download_url}', asset.download_url | default('')) + | replace('{photo_url}', asset.photo_url | default('')) + | replace('{playback_url}', asset.playback_url | default('')) + | replace('{people}', (asset.people | default([])) | join(', ')) + | replace('{album_name}', memory_current_album_name) + | replace('{album_created}', memory_current_album_created) + | replace('{album_updated}', memory_current_album_updated) + | replace('{is_favorite}', is_favorite) + | replace('{rating}', rating) + | replace('{location}', formatted_location) + | replace('{location_if_unique}', location_if_unique) + | replace('{city}', city) + | replace('{state}', state) + | replace('{country}', country) %} + {% set ns.items = ns.items ~ item %} + {% endfor %} + {{ ns.items }} + memory_message: >- + {%- set mem_common_date = (common_date_template | replace('{date}', memory_unique_dates[0])) if memory_unique_dates | length == 1 else '' -%} + {%- set mem_common_location = (common_location_template | replace('{location}', memory_unique_locations[0])) if memory_all_assets_have_location and memory_unique_locations | length == 1 else '' -%} + {{ memory_mode_message_template + | replace('{album_name}', memory_current_album_name) + | replace('{album_url}', memory_current_album_url) + | replace('{album_created}', memory_current_album_created) + | replace('{album_updated}', memory_current_album_updated) + | replace('{asset_count}', memory_fetched_count | string) + | replace('{common_date}', mem_common_date) + | replace('{common_location}', mem_common_location) + | replace('{assets}', memory_assets_list) }} + + # Send to notification targets + - service: notify.send_message + target: + entity_id: "{{ notify_targets }}" + data: + message: "{{ memory_message }}" + + # Send to Telegram with media if configured + - choose: + - conditions: + - condition: template + value_template: "{{ send_telegram_media and telegram_chat_ids | length > 0 }}" + sequence: + # Build media URLs for Telegram + - variables: + memory_media_urls: > + {% set ns = namespace(items = []) %} + {% for asset in memory_fetched_assets %} + {% set asset_type = asset.type | default('IMAGE') %} + {% set playback_url = asset.playback_url | default('') %} + {% set photo_url = asset.photo_url | default('') %} + {% set download_url = asset.download_url | default('') %} + {% set view_url = asset.url | default('') %} + {% if asset_type == 'VIDEO' and playback_url | length > 0 %} + {% set media_url = playback_url %} + {% elif asset_type == 'IMAGE' and photo_url | length > 0 %} + {% set media_url = photo_url %} + {% elif download_url | length > 0 %} + {% set media_url = download_url %} + {% else %} + {% set media_url = view_url %} + {% endif %} + {% if media_url | length > 0 %} + {% set media_type = 'video' if asset_type == 'VIDEO' else 'photo' %} + {% set item = {'url': media_url, 'type': media_type} %} + {% set ns.items = ns.items + [item] %} + {% endif %} + {% endfor %} + {{ ns.items }} + + # Send to each Telegram chat + - repeat: + for_each: "{{ telegram_chat_ids }}" + sequence: + # First send text message + - service: immich_album_watcher.send_telegram_notification + response_variable: telegram_memory_text_response + target: + entity_id: "{{ memory_current_album_entity }}" + data: + chat_id: "{{ repeat.item }}" + caption: "{{ memory_message }}" + disable_web_page_preview: "{{ telegram_disable_url_preview }}" + + # Extract message ID for reply + - variables: + memory_reply_to_id: "{{ telegram_memory_text_response[memory_current_album_entity].message_id | default(0) | int }}" + + # Send media if we have URLs + - choose: + - conditions: + - condition: template + value_template: "{{ memory_media_urls | length > 0 }}" + sequence: + - service: immich_album_watcher.send_telegram_notification + response_variable: telegram_memory_media_response + continue_on_error: true + target: + entity_id: "{{ memory_current_album_entity }}" + data: + chat_id: "{{ repeat.item }}" + urls: "{{ memory_media_urls }}" + reply_to_message_id: "{{ memory_reply_to_id }}" + max_group_size: "{{ max_media_per_group }}" + chunk_delay: "{{ telegram_media_delay }}" + wait_for_response: false + + # Combined Mode: Fetch from all albums and combine into one notification + - conditions: + - condition: template + value_template: "{{ memory_mode_album_mode == 'combined' }}" + sequence: + - variables: + memory_all_fetched_assets: [] + # Calculate per-album limit + memory_album_count: "{{ album_id_entities | length }}" + memory_per_album_limit: "{{ [((memory_mode_limit | int) // (memory_album_count | int)), 1] | max if memory_album_count | int > 0 else memory_mode_limit | int }}" + + # Fetch assets from each album + - repeat: + for_each: "{{ album_id_entities }}" + sequence: + - variables: + memory_combined_data: > + {% set data = { + 'limit': memory_per_album_limit | int, + 'favorite_only': memory_mode_favorite_only, + 'order_by': 'date', + 'order': 'ascending', + 'memory_date': now().strftime('%Y-%m-%d') + } %} + {% if memory_mode_type != 'all' %} + {% set data = dict(data, asset_type=memory_mode_type) %} + {% endif %} + {% if memory_mode_min_rating | int > 0 %} + {% set data = dict(data, filter_min_rating=memory_mode_min_rating | int) %} + {% endif %} + {{ data }} + + - service: immich_album_watcher.get_assets + response_variable: memory_combined_response + target: + entity_id: "{{ repeat.item }}" + data: "{{ memory_combined_data }}" + + # Filter out videos without playback URL + - variables: + memory_album_assets: > + {% set raw_assets = memory_combined_response[repeat.item].assets | default([]) %} + {% set ns = namespace(assets = []) %} + {% for asset in raw_assets %} + {% if asset.type == 'IMAGE' or (asset.type == 'VIDEO' and (asset.playback_url | default('') | length > 0)) %} + {% set ns.assets = ns.assets + [asset] %} + {% endif %} + {% endfor %} + {{ ns.assets }} + memory_all_fetched_assets: "{{ memory_all_fetched_assets + memory_album_assets }}" + + - variables: + # Trim to the actual limit + memory_all_fetched_assets: "{{ memory_all_fetched_assets[:memory_mode_limit | int] }}" + memory_combined_count: "{{ memory_all_fetched_assets | length }}" + # Build combined album names + memory_combined_album_names: > + {% set ns = namespace(names = []) %} + {% for entity_id in album_id_entities %} + {% set name_attr = state_attr(entity_id, 'album_name') %} + {% set name = name_attr if name_attr not in [none, '', 'unknown', 'unavailable'] else states(entity_id) %} + {% if name | length > 0 and name not in ['unknown', 'unavailable'] %} + {% set ns.names = ns.names + [name] %} + {% endif %} + {% endfor %} + {{ ns.names | join(', ') }} + + # Only send if we got assets + - choose: + - conditions: + - condition: template + value_template: "{{ memory_combined_count | int > 0 }}" + sequence: + # Format combined assets list + - variables: + memory_comb_unique_dates: > + {% set ns = namespace(dates = []) %} + {% for asset in memory_all_fetched_assets %} + {% set raw_date = asset.created_at | default('', true) %} + {% set dt = raw_date | as_datetime(none) if raw_date is string and raw_date | length > 0 else none %} + {% set formatted_date = dt.strftime(date_format) if dt else '' %} + {% if formatted_date | length > 0 and formatted_date not in ns.dates %} + {% set ns.dates = ns.dates + [formatted_date] %} + {% endif %} + {% endfor %} + {{ ns.dates }} + memory_comb_unique_locations: > + {% set ns = namespace(locations = []) %} + {% for asset in memory_all_fetched_assets %} + {% set city = asset.city | default('', true) %} + {% set state = asset.state | default('', true) %} + {% set country = asset.country | default('', true) %} + {% if city | length > 0 and state | length > 0 and country | length > 0 %} + {% set formatted_location = location_format | replace('{city}', city) | replace('{state}', state) | replace('{country}', country) %} + {% if formatted_location not in ns.locations %} + {% set ns.locations = ns.locations + [formatted_location] %} + {% endif %} + {% endif %} + {% endfor %} + {{ ns.locations }} + memory_comb_all_have_location: > + {% set ns = namespace(count = 0) %} + {% for asset in memory_all_fetched_assets %} + {% set city = asset.city | default('', true) %} + {% set state = asset.state | default('', true) %} + {% set country = asset.country | default('', true) %} + {% if city | length > 0 and state | length > 0 and country | length > 0 %} + {% set ns.count = ns.count + 1 %} + {% endif %} + {% endfor %} + {{ ns.count == memory_all_fetched_assets | length and memory_all_fetched_assets | length > 0 }} + memory_comb_assets_list: > + {% set ns = namespace(items = '') %} + {% for asset in memory_all_fetched_assets %} + {% set asset_template = message_asset_video_template if asset.type == 'VIDEO' else message_asset_image_template %} + {% set raw_date = asset.created_at | default('', true) %} + {% set dt = raw_date | as_datetime(none) if raw_date is string and raw_date | length > 0 else none %} + {% set formatted_date = dt.strftime(date_format) if dt else '' %} + {% set created_if_unique = '' if memory_comb_unique_dates | length == 1 else (date_if_unique_template | replace('{date}', formatted_date) if formatted_date | length > 0 else '') %} + {% set is_favorite = favorite_indicator_template if asset.is_favorite | default(false) else '' %} + {% set rating = asset.rating | default('') | string if asset.rating not in [none, ''] else '' %} + {% set city = asset.city | default('', true) %} + {% set state = asset.state | default('', true) %} + {% set country = asset.country | default('', true) %} + {% set has_full_location = city | length > 0 and state | length > 0 and country | length > 0 %} + {% set formatted_location = location_format | replace('{city}', city) | replace('{state}', state) | replace('{country}', country) if has_full_location else '' %} + {% set location_if_unique = '' if (not has_full_location) or (memory_comb_all_have_location and memory_comb_unique_locations | length == 1) else (location_if_unique_template | replace('{location}', formatted_location)) %} + {% set item = asset_template + | replace('{filename}', asset.filename | default('Unknown')) + | replace('{description}', asset.description | default('')) + | replace('{type}', asset.type | default('Unknown')) + | replace('{created}', formatted_date) + | replace('{created_if_unique}', created_if_unique) + | replace('{owner}', asset.owner | default('Unknown')) + | replace('{url}', asset.url | default('')) + | replace('{download_url}', asset.download_url | default('')) + | replace('{photo_url}', asset.photo_url | default('')) + | replace('{playback_url}', asset.playback_url | default('')) + | replace('{people}', (asset.people | default([])) | join(', ')) + | replace('{album_name}', memory_combined_album_names) + | replace('{album_created}', '') + | replace('{album_updated}', '') + | replace('{is_favorite}', is_favorite) + | replace('{rating}', rating) + | replace('{location}', formatted_location) + | replace('{location_if_unique}', location_if_unique) + | replace('{city}', city) + | replace('{state}', state) + | replace('{country}', country) %} + {% set ns.items = ns.items ~ item %} + {% endfor %} + {{ ns.items }} + memory_comb_message: >- + {%- set mcomb_common_date = (common_date_template | replace('{date}', memory_comb_unique_dates[0])) if memory_comb_unique_dates | length == 1 else '' -%} + {%- set mcomb_common_location = (common_location_template | replace('{location}', memory_comb_unique_locations[0])) if memory_comb_all_have_location and memory_comb_unique_locations | length == 1 else '' -%} + {{ memory_mode_message_template + | replace('{album_name}', memory_combined_album_names) + | replace('{album_url}', '') + | replace('{album_created}', '') + | replace('{album_updated}', '') + | replace('{asset_count}', memory_combined_count | string) + | replace('{common_date}', mcomb_common_date) + | replace('{common_location}', mcomb_common_location) + | replace('{assets}', memory_comb_assets_list) }} + + # Send to notification targets + - service: notify.send_message + target: + entity_id: "{{ notify_targets }}" + data: + message: "{{ memory_comb_message }}" + + # Send to Telegram with media if configured + - choose: + - conditions: + - condition: template + value_template: "{{ send_telegram_media and telegram_chat_ids | length > 0 and album_id_entities | length > 0 }}" + sequence: + # Build combined media URLs + - variables: + memory_comb_media_urls: > + {% set ns = namespace(items = []) %} + {% for asset in memory_all_fetched_assets %} + {% set asset_type = asset.type | default('IMAGE') %} + {% set playback_url = asset.playback_url | default('') %} + {% set photo_url = asset.photo_url | default('') %} + {% set download_url = asset.download_url | default('') %} + {% set view_url = asset.url | default('') %} + {% if asset_type == 'VIDEO' and playback_url | length > 0 %} + {% set media_url = playback_url %} + {% elif asset_type == 'IMAGE' and photo_url | length > 0 %} + {% set media_url = photo_url %} + {% elif download_url | length > 0 %} + {% set media_url = download_url %} + {% else %} + {% set media_url = view_url %} + {% endif %} + {% if media_url | length > 0 %} + {% set media_type = 'video' if asset_type == 'VIDEO' else 'photo' %} + {% set item = {'url': media_url, 'type': media_type} %} + {% set ns.items = ns.items + [item] %} + {% endif %} + {% endfor %} + {{ ns.items }} + + # Send to each Telegram chat + - repeat: + for_each: "{{ telegram_chat_ids }}" + sequence: + - service: immich_album_watcher.send_telegram_notification + response_variable: telegram_memory_comb_text_response + target: + entity_id: "{{ album_id_entities[0] }}" + data: + chat_id: "{{ repeat.item }}" + caption: "{{ memory_comb_message }}" + disable_web_page_preview: "{{ telegram_disable_url_preview }}" + + - variables: + memory_comb_reply_to_id: "{{ telegram_memory_comb_text_response[album_id_entities[0]].message_id | default(0) | int }}" + + - choose: + - conditions: + - condition: template + value_template: "{{ memory_comb_media_urls | length > 0 }}" + sequence: + - service: immich_album_watcher.send_telegram_notification + response_variable: telegram_memory_comb_media_response + continue_on_error: true + target: + entity_id: "{{ album_id_entities[0] }}" + data: + chat_id: "{{ repeat.item }}" + urls: "{{ memory_comb_media_urls }}" + reply_to_message_id: "{{ memory_comb_reply_to_id }}" + max_group_size: "{{ max_media_per_group }}" + chunk_delay: "{{ telegram_media_delay }}" + wait_for_response: false + # Stop here if this was a scheduled trigger - don't continue to event-based actions - choose: - conditions: - condition: template - value_template: "{{ trigger.id in ['periodic_summary', 'scheduled_assets'] }}" + value_template: "{{ trigger.id in ['periodic_summary', 'scheduled_assets', 'memory_mode'] }}" sequence: - stop: "Scheduled notification sent" diff --git a/manifest.json b/manifest.json index 39708a0..61f204c 100644 --- a/manifest.json +++ b/manifest.json @@ -1,3 +1,3 @@ { - "version": "1.24.0" + "version": "1.25.0" }