Add Memory Mode as separate feature with independent schedule

Replace the previous memory order_by option with a dedicated Memory Mode
feature that has its own enable toggle, schedule (interval/start hour),
and settings. Users can now run both Scheduled Assets and Memory Mode
independently at different times.

Memory Mode fetches photos taken on today's date in previous years using
the memory_date parameter, sorted by date ascending (oldest first).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-02 14:23:57 +03:00
parent 9bb7824a38
commit b48d78a188
3 changed files with 706 additions and 30 deletions

View File

@@ -199,19 +199,12 @@ Sends scheduled notifications with existing assets from tracked albums. Uses the
| `limit` | Maximum number of assets to fetch (1-100) | | `limit` | Maximum number of assets to fetch (1-100) |
| `favorite_only` | Only fetch favorite assets | | `favorite_only` | Only fetch favorite assets |
| `asset_type` | Filter by type (all, photo, video) | | `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) | | `order` | Sort direction (ascending, descending) |
| `filter_min_rating` | Minimum rating filter (1-5, 0 = no filter) | | `filter_min_rating` | Minimum rating filter (1-5, 0 = no filter) |
| `min_date` | Assets created on or after this date (YYYY-MM-DD) | | `min_date` | Assets created on or after this date (YYYY-MM-DD) |
| `max_date` | Assets created on or before 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 ### Scheduled Assets Message Template Variables
| Variable | Description | | 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"}` - **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 ## Author
Alexei Dolgolyov (dolgolyov.alexei@gmail.com) Alexei Dolgolyov (dolgolyov.alexei@gmail.com)

View File

@@ -567,9 +567,7 @@ blueprint:
scheduled_assets_order_by: scheduled_assets_order_by:
name: Order By name: Order By
description: > description: "How to sort/select assets"
How to sort/select assets.
Memory mode fetches photos taken on today's date in previous years ("On This Day").
default: "random" default: "random"
selector: selector:
select: select:
@@ -582,8 +580,6 @@ blueprint:
value: "rating" value: "rating"
- label: "Name" - label: "Name"
value: "name" value: "name"
- label: "Memory (On This Day)"
value: "memory"
scheduled_assets_order: scheduled_assets_order:
name: Order Direction name: Order Direction
@@ -642,6 +638,119 @@ blueprint:
text: text:
multiline: true 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 # Debug
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
@@ -722,6 +831,13 @@ trigger:
event_type: immich_album_watcher_test_scheduled_assets event_type: immich_album_watcher_test_scheduled_assets
id: "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 # VARIABLES
# ============================================================================= # =============================================================================
@@ -788,6 +904,17 @@ variables:
scheduled_assets_max_date: !input scheduled_assets_max_date scheduled_assets_max_date: !input scheduled_assets_max_date
scheduled_assets_message_template: !input scheduled_assets_message 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)") # Parse chat IDs from notify entity friendly names (format: "Name (123456789)")
# and combine with chat IDs from input_text entities # and combine with chat IDs from input_text entities
telegram_chat_ids: > telegram_chat_ids: >
@@ -1143,20 +1270,54 @@ variables:
{{ album_id_entities }} {{ album_id_entities }}
{% endif %} {% 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 # CONDITIONS
# ============================================================================= # =============================================================================
condition: condition:
# Allow through if: # 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) # 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 - condition: template
value_template: > value_template: >
{% if trigger.id == 'scheduled_assets' %} {% if trigger.id == 'scheduled_assets' %}
{{ should_send_scheduled_assets }} {{ should_send_scheduled_assets }}
{% elif trigger.id == 'memory_mode' %}
{{ should_send_memory_mode }}
{% elif trigger.id in ['hourly_timer', 'periodic_summary'] %} {% 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 %} {% else %}
{{ is_hub_tracked and is_album_tracked and should_notify }} {{ is_hub_tracked and is_album_tracked and should_notify }}
{% endif %} {% endif %}
@@ -1237,16 +1398,12 @@ action:
# Build service data dynamically (omit optional params when not set) # Build service data dynamically (omit optional params when not set)
- variables: - variables:
get_assets_data: > get_assets_data: >
{% set is_memory_mode = scheduled_assets_order_by == 'memory' %}
{% set data = { {% set data = {
'limit': scheduled_assets_limit | int, 'limit': scheduled_assets_limit | int,
'favorite_only': scheduled_assets_favorite_only, 'favorite_only': scheduled_assets_favorite_only,
'order_by': 'date' if is_memory_mode else scheduled_assets_order_by, 'order_by': scheduled_assets_order_by,
'order': 'ascending' if is_memory_mode else scheduled_assets_order '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' %} {% if scheduled_assets_type != 'all' %}
{% set data = dict(data, asset_type=scheduled_assets_type) %} {% set data = dict(data, asset_type=scheduled_assets_type) %}
{% endif %} {% endif %}
@@ -1478,16 +1635,12 @@ action:
# Build service data dynamically (omit optional params when not set) # Build service data dynamically (omit optional params when not set)
- variables: - variables:
combined_get_assets_data: > combined_get_assets_data: >
{% set is_memory_mode = scheduled_assets_order_by == 'memory' %}
{% set data = { {% set data = {
'limit': per_album_limit | int, 'limit': per_album_limit | int,
'favorite_only': scheduled_assets_favorite_only, 'favorite_only': scheduled_assets_favorite_only,
'order_by': 'date' if is_memory_mode else scheduled_assets_order_by, 'order_by': scheduled_assets_order_by,
'order': 'ascending' if is_memory_mode else scheduled_assets_order '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' %} {% if scheduled_assets_type != 'all' %}
{% set data = dict(data, asset_type=scheduled_assets_type) %} {% set data = dict(data, asset_type=scheduled_assets_type) %}
{% endif %} {% endif %}
@@ -1712,11 +1865,501 @@ action:
chunk_delay: "{{ telegram_media_delay }}" chunk_delay: "{{ telegram_media_delay }}"
wait_for_response: false 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 # Stop here if this was a scheduled trigger - don't continue to event-based actions
- choose: - choose:
- conditions: - conditions:
- condition: template - condition: template
value_template: "{{ trigger.id in ['periodic_summary', 'scheduled_assets'] }}" value_template: "{{ trigger.id in ['periodic_summary', 'scheduled_assets', 'memory_mode'] }}"
sequence: sequence:
- stop: "Scheduled notification sent" - stop: "Scheduled notification sent"

View File

@@ -1,3 +1,3 @@
{ {
"version": "1.24.0" "version": "1.25.0"
} }