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');
|
let formType = $state<'telegram' | 'webhook'>('telegram');
|
||||||
const defaultForm = () => ({ name: '', icon: '', bot_id: 0, chat_id: '', bot_token: '', url: '', headers: '',
|
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,
|
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 form = $state(defaultForm());
|
||||||
let error = $state('');
|
let error = $state('');
|
||||||
let headersError = $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,
|
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,
|
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,
|
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;
|
editing = tgt.id; showTelegramSettings = false; showForm = true;
|
||||||
if (form.bot_id) await loadBotChats();
|
if (form.bot_id) await loadBotChats();
|
||||||
|
|||||||
@@ -177,7 +177,7 @@
|
|||||||
.replace(/</g, '<')
|
.replace(/</g, '<')
|
||||||
.replace(/>/g, '>')
|
.replace(/>/g, '>')
|
||||||
// Restore allowed tags — only http(s) URLs for <a> to prevent javascript: XSS
|
// 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(/<\/a>/g, '</a>')
|
||||||
.replace(/<b>/g, '<b>').replace(/<\/b>/g, '</b>')
|
.replace(/<b>/g, '<b>').replace(/<\/b>/g, '</b>')
|
||||||
.replace(/<i>/g, '<i>').replace(/<\/i>/g, '</i>')
|
.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_date %} 📅 {{ common_date }}{% endif %}
|
||||||
{%- if common_location %} 📍 {{ common_location }}{% endif %}
|
{%- if common_location %} 📍 {{ common_location }}{% endif %}
|
||||||
{%- if people %}
|
{%- if people %}
|
||||||
👤 {{ people | join(", ") }}
|
👤 {{ people | join(", ") }}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
{%- if public_url %}
|
|
||||||
🔗 <a href="{{ public_url }}">Album URL</a>
|
|
||||||
{%- endif %}
|
|
||||||
{%- if added_assets %}
|
{%- if added_assets %}
|
||||||
{%- for asset in 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 %}
|
• {%- 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_date %} 📅 {{ common_date }}{% endif %}
|
||||||
{%- if common_location %} 📍 {{ common_location }}{% endif %}
|
{%- if common_location %} 📍 {{ common_location }}{% endif %}
|
||||||
{%- if people %}
|
{%- if people %}
|
||||||
👤 {{ people | join(", ") }}
|
👤 {{ people | join(", ") }}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
{%- if public_url %}
|
|
||||||
🔗 <a href="{{ public_url }}">Ссылка на альбом</a>
|
|
||||||
{%- endif %}
|
|
||||||
{%- if added_assets %}
|
{%- if added_assets %}
|
||||||
{%- for asset in 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 %}
|
• {%- 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
|
name: str
|
||||||
icon: str = Field(default="")
|
icon: str = Field(default="")
|
||||||
config: dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
|
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)
|
created_at: datetime = Field(default_factory=_utcnow)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ async def health():
|
|||||||
|
|
||||||
|
|
||||||
async def _seed_default_templates():
|
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 import func, select
|
||||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
from .database.engine import get_engine
|
from .database.engine import get_engine
|
||||||
@@ -89,22 +89,36 @@ async def _seed_default_templates():
|
|||||||
async with AsyncSession(engine) as session:
|
async with AsyncSession(engine) as session:
|
||||||
result = await session.exec(select(func.count()).select_from(TemplateConfig))
|
result = await session.exec(select(func.count()).select_from(TemplateConfig))
|
||||||
count = result.one()
|
count = result.one()
|
||||||
if count > 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
for locale in ("en", "ru"):
|
if count == 0:
|
||||||
slots = load_default_templates(locale)
|
# First startup — seed all defaults
|
||||||
if not slots:
|
for locale in ("en", "ru"):
|
||||||
continue
|
slots = load_default_templates(locale)
|
||||||
name = f"Default ({locale.upper()})"
|
if not slots:
|
||||||
config = TemplateConfig(
|
continue
|
||||||
user_id=0,
|
name = f"Default ({locale.upper()})"
|
||||||
provider_type="immich",
|
config = TemplateConfig(
|
||||||
name=name,
|
user_id=0,
|
||||||
description=f"Default Immich templates ({locale.upper()})",
|
provider_type="immich",
|
||||||
**slots,
|
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()
|
await session.commit()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user