From 371ea70756345d25dbf4cee5d4b9478a4177c141 Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Sat, 21 Mar 2026 01:37:58 +0300 Subject: [PATCH] feat: fix template preview links, default chat action, update default templates - Fix sanitizePreview regex to match literal quotes instead of " entities - Default telegram chat_action to "typing" in model and frontend - Change "photo(s)" to "file(s)" in default templates (EN/RU) - Remove redundant album URL line from assets_added templates - Auto-refresh system-owned templates from files on server startup Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/src/routes/targets/+page.svelte | 4 +- .../src/routes/template-configs/+page.svelte | 2 +- .../templates/defaults/en/assets_added.jinja2 | 5 +-- .../defaults/en/assets_removed.jinja2 | 2 +- .../templates/defaults/ru/assets_added.jinja2 | 5 +-- .../defaults/ru/assets_removed.jinja2 | 2 +- .../notify_bridge_server/database/models.py | 2 +- .../server/src/notify_bridge_server/main.py | 44 ++++++++++++------- 8 files changed, 37 insertions(+), 29 deletions(-) diff --git a/frontend/src/routes/targets/+page.svelte b/frontend/src/routes/targets/+page.svelte index fe5d2e8..2fd5e43 100644 --- a/frontend/src/routes/targets/+page.svelte +++ b/frontend/src/routes/targets/+page.svelte @@ -23,7 +23,7 @@ let formType = $state<'telegram' | 'webhook'>('telegram'); const defaultForm = () => ({ name: '', icon: '', bot_id: 0, chat_id: '', bot_token: '', url: '', headers: '', max_media_to_send: 50, max_media_per_group: 10, media_delay: 500, max_asset_size: 50, - disable_url_preview: false, send_large_photos_as_documents: false, ai_captions: false, chat_action: '' }); + disable_url_preview: false, send_large_photos_as_documents: false, ai_captions: false, chat_action: 'typing' }); let form = $state(defaultForm()); let error = $state(''); let headersError = $state(''); @@ -55,7 +55,7 @@ max_media_to_send: c.max_media_to_send ?? 50, max_media_per_group: c.max_media_per_group ?? 10, media_delay: c.media_delay ?? 500, max_asset_size: c.max_asset_size ?? 50, disable_url_preview: c.disable_url_preview ?? false, send_large_photos_as_documents: c.send_large_photos_as_documents ?? false, - ai_captions: c.ai_captions ?? false, chat_action: c.chat_action ?? '', + ai_captions: c.ai_captions ?? false, chat_action: c.chat_action ?? 'typing', }; editing = tgt.id; showTelegramSettings = false; showForm = true; if (form.bot_id) await loadBotChats(); diff --git a/frontend/src/routes/template-configs/+page.svelte b/frontend/src/routes/template-configs/+page.svelte index 43b75a9..07778f0 100644 --- a/frontend/src/routes/template-configs/+page.svelte +++ b/frontend/src/routes/template-configs/+page.svelte @@ -177,7 +177,7 @@ .replace(//g, '>') // Restore allowed tags — only http(s) URLs for to prevent javascript: XSS - .replace(/<a href="(https?:\/\/[^&]*)">/g, '') + .replace(/<a href="(https?:\/\/[^"]*)">/g, '') .replace(/<\/a>/g, '') .replace(/<b>/g, '').replace(/<\/b>/g, '') .replace(/<i>/g, '').replace(/<\/i>/g, '') diff --git a/packages/core/src/notify_bridge_core/templates/defaults/en/assets_added.jinja2 b/packages/core/src/notify_bridge_core/templates/defaults/en/assets_added.jinja2 index 3fa0b59..298fcf1 100644 --- a/packages/core/src/notify_bridge_core/templates/defaults/en/assets_added.jinja2 +++ b/packages/core/src/notify_bridge_core/templates/defaults/en/assets_added.jinja2 @@ -1,12 +1,9 @@ -📷 {{ added_count }} new photo(s) added to album {% if public_url %}{{ album_name }}{% else %}"{{ album_name }}"{% endif %}. +📷 {{ added_count }} new file(s) added to album {% if public_url %}{{ album_name }}{% else %}"{{ album_name }}"{% endif %}. {%- if common_date %} 📅 {{ common_date }}{% endif %} {%- if common_location %} 📍 {{ common_location }}{% endif %} {%- if people %} 👤 {{ people | join(", ") }} {%- endif %} -{%- if public_url %} -🔗 Album URL -{%- endif %} {%- if added_assets %} {%- for asset in added_assets %} • {%- if asset.type == "VIDEO" %} 🎬{% else %} 🖼️{% endif %} {% if asset.public_url %}{{ asset.filename }}{% else %}{{ asset.filename }}{% endif %} diff --git a/packages/core/src/notify_bridge_core/templates/defaults/en/assets_removed.jinja2 b/packages/core/src/notify_bridge_core/templates/defaults/en/assets_removed.jinja2 index 7442811..faa5441 100644 --- a/packages/core/src/notify_bridge_core/templates/defaults/en/assets_removed.jinja2 +++ b/packages/core/src/notify_bridge_core/templates/defaults/en/assets_removed.jinja2 @@ -1 +1 @@ -🗑️ {{ removed_count }} photo(s) removed from album {% if public_url %}{{ album_name }}{% else %}"{{ album_name }}"{% endif %}. \ No newline at end of file +🗑️ {{ removed_count }} file(s) removed from album {% if public_url %}{{ album_name }}{% else %}"{{ album_name }}"{% endif %}. \ No newline at end of file diff --git a/packages/core/src/notify_bridge_core/templates/defaults/ru/assets_added.jinja2 b/packages/core/src/notify_bridge_core/templates/defaults/ru/assets_added.jinja2 index 2b30ee0..5abafb7 100644 --- a/packages/core/src/notify_bridge_core/templates/defaults/ru/assets_added.jinja2 +++ b/packages/core/src/notify_bridge_core/templates/defaults/ru/assets_added.jinja2 @@ -1,12 +1,9 @@ -📷 {{ added_count }} новых фото добавлено в альбом {% if public_url %}{{ album_name }}{% else %}"{{ album_name }}"{% endif %}. +📷 {{ added_count }} новых файл(ов) добавлено в альбом {% if public_url %}{{ album_name }}{% else %}"{{ album_name }}"{% endif %}. {%- if common_date %} 📅 {{ common_date }}{% endif %} {%- if common_location %} 📍 {{ common_location }}{% endif %} {%- if people %} 👤 {{ people | join(", ") }} {%- endif %} -{%- if public_url %} -🔗 Ссылка на альбом -{%- endif %} {%- if added_assets %} {%- for asset in added_assets %} • {%- if asset.type == "VIDEO" %} 🎬{% else %} 🖼️{% endif %} {% if asset.public_url %}{{ asset.filename }}{% else %}{{ asset.filename }}{% endif %} diff --git a/packages/core/src/notify_bridge_core/templates/defaults/ru/assets_removed.jinja2 b/packages/core/src/notify_bridge_core/templates/defaults/ru/assets_removed.jinja2 index b0fb33f..3c2aaf2 100644 --- a/packages/core/src/notify_bridge_core/templates/defaults/ru/assets_removed.jinja2 +++ b/packages/core/src/notify_bridge_core/templates/defaults/ru/assets_removed.jinja2 @@ -1 +1 @@ -🗑️ {{ removed_count }} фото удалено из альбома {% if public_url %}{{ album_name }}{% else %}"{{ album_name }}"{% endif %}. \ No newline at end of file +🗑️ {{ removed_count }} файл(ов) удалено из альбома {% if public_url %}{{ album_name }}{% else %}"{{ album_name }}"{% endif %}. \ No newline at end of file diff --git a/packages/server/src/notify_bridge_server/database/models.py b/packages/server/src/notify_bridge_server/database/models.py index 8bfb945..1b1a317 100644 --- a/packages/server/src/notify_bridge_server/database/models.py +++ b/packages/server/src/notify_bridge_server/database/models.py @@ -164,7 +164,7 @@ class NotificationTarget(SQLModel, table=True): name: str icon: str = Field(default="") config: dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON)) - chat_action: str | None = Field(default=None) # e.g. "typing", "upload_photo" + chat_action: str | None = Field(default="typing") # e.g. "typing", "upload_photo" created_at: datetime = Field(default_factory=_utcnow) diff --git a/packages/server/src/notify_bridge_server/main.py b/packages/server/src/notify_bridge_server/main.py index 7e7d9f5..4bd1d78 100644 --- a/packages/server/src/notify_bridge_server/main.py +++ b/packages/server/src/notify_bridge_server/main.py @@ -78,7 +78,7 @@ async def health(): async def _seed_default_templates(): - """Seed default templates on first startup if none exist.""" + """Seed or update default (system-owned) templates on startup.""" from sqlmodel import func, select from sqlmodel.ext.asyncio.session import AsyncSession from .database.engine import get_engine @@ -89,22 +89,36 @@ async def _seed_default_templates(): async with AsyncSession(engine) as session: result = await session.exec(select(func.count()).select_from(TemplateConfig)) count = result.one() - if count > 0: - return - for locale in ("en", "ru"): - slots = load_default_templates(locale) - if not slots: - continue - name = f"Default ({locale.upper()})" - config = TemplateConfig( - user_id=0, - provider_type="immich", - name=name, - description=f"Default Immich templates ({locale.upper()})", - **slots, + if count == 0: + # First startup — seed all defaults + for locale in ("en", "ru"): + slots = load_default_templates(locale) + if not slots: + continue + name = f"Default ({locale.upper()})" + config = TemplateConfig( + user_id=0, + provider_type="immich", + name=name, + description=f"Default Immich templates ({locale.upper()})", + **slots, + ) + session.add(config) + else: + # Update existing system-owned templates from files + result = await session.exec( + select(TemplateConfig).where(TemplateConfig.user_id == 0) ) - session.add(config) + system_configs = result.all() + for config in system_configs: + locale = "ru" if "(RU)" in config.name else "en" + slots = load_default_templates(locale) + if not slots: + continue + for key, value in slots.items(): + setattr(config, key, value) + session.add(config) await session.commit()