Add reverse geocoding location support and fix NoneType error

- Add location template inputs (common_location_template, location_if_unique_template, location_format)
- Display common location in header when all assets share the same location
- Show per-asset location when locations differ using {location_if_unique}
- Location only shown when all three fields (city, state, country) are present
- Fix TypeError on NoneType length check when asset.created_at is null
- Add defensive checks for date parsing to prevent template errors

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-01 02:51:15 +03:00
parent 7b00899903
commit e63bb3da95
3 changed files with 132 additions and 16 deletions

View File

@@ -147,8 +147,8 @@ blueprint:
name: "Assets Added Message"
description: >
Message sent when assets are added to an album.
Variables: `{album_name}`, `{album_url}`, `{added_count}`, `{people}`, `{assets}`, `{video_warning}`, `{common_date}`
default: "📷 {added_count} new photo(s) added to album \"{album_name}\".{people}{assets}{video_warning}"
Variables: `{album_name}`, `{album_url}`, `{added_count}`, `{people}`, `{assets}`, `{video_warning}`, `{common_date}`, `{common_location}`
default: "📷 {added_count} new photo(s) added to album \"{album_name}\"{common_date}{common_location}.{people}{assets}{video_warning}"
selector:
text:
multiline: true
@@ -276,6 +276,37 @@ blueprint:
selector:
text:
common_location_template:
name: "Common Location Template"
description: >
Template for showing the common location in the header message when all assets
share the same location. Only used when ALL location fields (city, state, country)
are present for all assets. Use `{location}` as the placeholder.
Leave empty to not show common location in header.
default: " in {location}"
selector:
text:
location_if_unique_template:
name: "Location If Unique Template"
description: >
Template for showing location in asset items when assets have different locations.
Only used when ALL location fields (city, state, country) are present.
Use `{location}` as the placeholder.
Leave empty to not show per-asset locations.
default: " 📍 {location}"
selector:
text:
location_format:
name: "Location Format"
description: >
Format for displaying asset locations. Available placeholders:
`{city}`, `{state}`, `{country}`. Only shown when all three fields are present.
default: "{city}, {country}"
selector:
text:
# -------------------------------------------------------------------------
# Telegram Media Attachments
# -------------------------------------------------------------------------
@@ -788,6 +819,9 @@ variables:
common_date_template: !input common_date_template
date_if_unique_template: !input date_if_unique_template
favorite_indicator_template: !input favorite_indicator_template
common_location_template: !input common_location_template
location_if_unique_template: !input location_if_unique_template
location_format: !input location_format
# ---------------------------------------------------------------------------
# Event Data
@@ -843,8 +877,8 @@ variables:
unique_dates: >
{% set ns = namespace(dates = []) %}
{% for asset in filtered_assets %}
{% set raw_date = asset.created_at | default('') %}
{% set dt = raw_date | as_datetime(none) if raw_date | length > 0 else none %}
{% 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] %}
@@ -863,6 +897,46 @@ variables:
{{ '' }}
{% endif %}
# Compute unique locations from filtered assets (only when all location fields present)
unique_locations: >
{% set ns = namespace(locations = []) %}
{% for asset in filtered_assets %}
{% set city = asset.city | default('') %}
{% set state = asset.state | default('') %}
{% set country = asset.country | default('') %}
{% 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 assets with location data share the same location
all_locations_same: "{{ unique_locations | length == 1 }}"
# Check if all filtered assets have complete location data
all_assets_have_location: >
{% set ns = namespace(count = 0) %}
{% for asset in filtered_assets %}
{% set city = asset.city | default('') %}
{% set state = asset.state | default('') %}
{% set country = asset.country | default('') %}
{% if city | length > 0 and state | length > 0 and country | length > 0 %}
{% set ns.count = ns.count + 1 %}
{% endif %}
{% endfor %}
{{ ns.count == filtered_assets | length and filtered_assets | length > 0 }}
# Common location for header (formatted with template if all same, empty otherwise)
common_location: >
{% if all_assets_have_location and unique_locations | length == 1 %}
{{ common_location_template | replace('{location}', unique_locations[0]) }}
{% else %}
{{ '' }}
{% endif %}
# Format assets list for notification
assets_list: >
{% if include_asset_details and filtered_assets | length > 0 %}
@@ -871,12 +945,18 @@ variables:
{% set assets_to_show = filtered_assets[:max_items] %}
{% for asset in assets_to_show %}
{% set asset_template = message_asset_video_template if asset.type == 'VIDEO' else message_asset_image_template %}
{% set raw_date = asset.created_at | default('') %}
{% set dt = raw_date | as_datetime(none) if raw_date | length > 0 else none %}
{% 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 unique_dates | length == 1 else (date_if_unique_template | replace('{date}', formatted_date)) %}
{% 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('') %}
{% set state = asset.state | default('') %}
{% set country = asset.country | default('') %}
{% 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 (all_assets_have_location and 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(''))
@@ -891,7 +971,12 @@ variables:
| replace('{people}', (asset.people | default([])) | join(', '))
| replace('{album_name}', event_album_name)
| replace('{is_favorite}', is_favorite)
| replace('{rating}', rating) %}
| 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 %}
{% set more_count = filtered_assets | length - max_items %}
@@ -1164,11 +1249,16 @@ action:
{% set ns = namespace(items = '') %}
{% for asset in 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('') %}
{% set dt = raw_date | as_datetime(none) if raw_date | length > 0 else none %}
{% 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 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('') %}
{% set state = asset.state | default('') %}
{% set country = asset.country | default('') %}
{% 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 item = asset_template
| replace('{filename}', asset.filename | default('Unknown'))
| replace('{description}', asset.description | default(''))
@@ -1183,7 +1273,12 @@ action:
| replace('{people}', (asset.people | default([])) | join(', '))
| replace('{album_name}', current_album_name)
| replace('{is_favorite}', is_favorite)
| replace('{rating}', rating) %}
| replace('{rating}', rating)
| replace('{location}', formatted_location)
| replace('{location_if_unique}', formatted_location)
| replace('{city}', city)
| replace('{state}', state)
| replace('{country}', country) %}
{% set ns.items = ns.items ~ item %}
{% endfor %}
{{ ns.items }}
@@ -1348,11 +1443,16 @@ action:
{% set ns = namespace(items = '') %}
{% for asset in 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('') %}
{% set dt = raw_date | as_datetime(none) if raw_date | length > 0 else none %}
{% 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 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('') %}
{% set state = asset.state | default('') %}
{% set country = asset.country | default('') %}
{% 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 item = asset_template
| replace('{filename}', asset.filename | default('Unknown'))
| replace('{description}', asset.description | default(''))
@@ -1367,7 +1467,12 @@ action:
| replace('{people}', (asset.people | default([])) | join(', '))
| replace('{album_name}', combined_album_names)
| replace('{is_favorite}', is_favorite)
| replace('{rating}', rating) %}
| replace('{rating}', rating)
| replace('{location}', formatted_location)
| replace('{location_if_unique}', formatted_location)
| replace('{city}', city)
| replace('{state}', state)
| replace('{country}', country) %}
{% set ns.items = ns.items ~ item %}
{% endfor %}
{{ ns.items }}
@@ -1523,7 +1628,8 @@ action:
| replace('{people}', people_list)
| replace('{assets}', assets_list)
| replace('{video_warning}', video_warning_text)
| replace('{common_date}', common_date) }}
| replace('{common_date}', common_date)
| replace('{common_location}', common_location) }}
- service: notify.send_message
target:
@@ -1748,7 +1854,8 @@ action:
| replace('{people}', people_list)
| replace('{assets}', assets_list)
| replace('{video_warning}', video_warning_text)
| replace('{common_date}', common_date) }}
| replace('{common_date}', common_date)
| replace('{common_location}', common_location) }}
# Build URLs list for media
# URL preference: playback_url (videos), photo_url (images) > download_url > url