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) <noreply@anthropic.com>
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -177,7 +177,7 @@
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
// Restore allowed tags — only http(s) URLs for <a> to prevent javascript: XSS
|
||||
.replace(/<a href="(https?:\/\/[^&]*)">/g, '<a href="$1" target="_blank" rel="noopener noreferrer">')
|
||||
.replace(/<a href="(https?:\/\/[^"]*)">/g, '<a href="$1" target="_blank" rel="noopener noreferrer">')
|
||||
.replace(/<\/a>/g, '</a>')
|
||||
.replace(/<b>/g, '<b>').replace(/<\/b>/g, '</b>')
|
||||
.replace(/<i>/g, '<i>').replace(/<\/i>/g, '</i>')
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
📷 {{ added_count }} new photo(s) added to album {% if public_url %}<a href="{{ public_url }}">{{ album_name }}</a>{% else %}"{{ album_name }}"{% endif %}.
|
||||
📷 {{ added_count }} new file(s) added to album {% if public_url %}<a href="{{ public_url }}">{{ album_name }}</a>{% else %}"{{ album_name }}"{% endif %}.
|
||||
{%- if common_date %} 📅 {{ common_date }}{% endif %}
|
||||
{%- if common_location %} 📍 {{ common_location }}{% endif %}
|
||||
{%- if people %}
|
||||
👤 {{ people | join(", ") }}
|
||||
{%- endif %}
|
||||
{%- if public_url %}
|
||||
🔗 <a href="{{ public_url }}">Album URL</a>
|
||||
{%- endif %}
|
||||
{%- if added_assets %}
|
||||
{%- for asset in added_assets %}
|
||||
• {%- if asset.type == "VIDEO" %} 🎬{% else %} 🖼️{% endif %} {% if asset.public_url %}<a href="{{ asset.public_url }}">{{ asset.filename }}</a>{% else %}{{ asset.filename }}{% endif %}
|
||||
|
||||
@@ -1 +1 @@
|
||||
🗑️ {{ removed_count }} photo(s) removed from album {% if public_url %}<a href="{{ public_url }}">{{ album_name }}</a>{% else %}"{{ album_name }}"{% endif %}.
|
||||
🗑️ {{ removed_count }} file(s) removed from album {% if public_url %}<a href="{{ public_url }}">{{ album_name }}</a>{% else %}"{{ album_name }}"{% endif %}.
|
||||
@@ -1,12 +1,9 @@
|
||||
📷 {{ added_count }} новых фото добавлено в альбом {% if public_url %}<a href="{{ public_url }}">{{ album_name }}</a>{% else %}"{{ album_name }}"{% endif %}.
|
||||
📷 {{ added_count }} новых файл(ов) добавлено в альбом {% if public_url %}<a href="{{ public_url }}">{{ album_name }}</a>{% else %}"{{ album_name }}"{% endif %}.
|
||||
{%- if common_date %} 📅 {{ common_date }}{% endif %}
|
||||
{%- if common_location %} 📍 {{ common_location }}{% endif %}
|
||||
{%- if people %}
|
||||
👤 {{ people | join(", ") }}
|
||||
{%- endif %}
|
||||
{%- if public_url %}
|
||||
🔗 <a href="{{ public_url }}">Ссылка на альбом</a>
|
||||
{%- endif %}
|
||||
{%- if added_assets %}
|
||||
{%- for asset in added_assets %}
|
||||
• {%- if asset.type == "VIDEO" %} 🎬{% else %} 🖼️{% endif %} {% if asset.public_url %}<a href="{{ asset.public_url }}">{{ asset.filename }}</a>{% else %}{{ asset.filename }}{% endif %}
|
||||
|
||||
@@ -1 +1 @@
|
||||
🗑️ {{ removed_count }} фото удалено из альбома {% if public_url %}<a href="{{ public_url }}">{{ album_name }}</a>{% else %}"{{ album_name }}"{% endif %}.
|
||||
🗑️ {{ removed_count }} файл(ов) удалено из альбома {% if public_url %}<a href="{{ public_url }}">{{ album_name }}</a>{% else %}"{{ album_name }}"{% endif %}.
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user