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

@@ -71,6 +71,9 @@ Each item in `added_assets` contains:
| `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 |
| `city` | City name from reverse geocoding (if available) |
| `state` | State/region name from reverse geocoding (if available) |
| `country` | Country name from reverse geocoding (if available) |
## Message Template Variables
@@ -86,6 +89,7 @@ All message templates support these placeholder variables (use single braces):
| `{assets}` | Formatted list of added assets (using asset item template) |
| `{video_warning}` | Warning about video size limits (Telegram only, empty otherwise) |
| `{common_date}` | Common date formatted with template if all assets share same date, empty otherwise |
| `{common_location}` | Common location formatted with template if all assets share same location (requires city, state, country), empty otherwise |
## Asset Item Template Variables
@@ -107,6 +111,11 @@ These variables can be used in the image and video asset templates. Also used fo
| `{album_name}` | Name of the album |
| `{is_favorite}` | Favorite indicator (using template) if asset is favorite, empty otherwise |
| `{rating}` | User rating (1-5) or empty if not rated |
| `{location}` | Formatted location string (using location format template), empty if location data incomplete |
| `{location_if_unique}` | Location formatted with template if locations differ between assets, empty if all same or data incomplete |
| `{city}` | City name from reverse geocoding, empty if not available |
| `{state}` | State/region name from reverse geocoding, empty if not available |
| `{country}` | Country name from reverse geocoding, empty if not available |
## Telegram Media Attachments

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