feat: telegram commands, app settings, bot polling, webhook handling, UI improvements

Adds telegram bot command system with 13 commands (search, latest, random, etc.),
webhook/polling handlers, rate limiting, app settings page, and various UI/UX
improvements across all entity pages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-20 23:11:42 +03:00
parent 5015e378fe
commit 03ec9b3c86
64 changed files with 2585 additions and 648 deletions
+8 -34
View File
@@ -67,7 +67,7 @@
},
});
onMount(() => {
function buildExtensions(isDark: boolean) {
const extensions = [
jinjaLang,
errorLineField,
@@ -88,17 +88,14 @@
'.ͼ5': { color: '#6b7280' },
}),
];
if (isDark) extensions.push(oneDark);
if (placeholder) extensions.push(cmPlaceholder(placeholder));
return extensions;
}
if (theme.isDark) {
extensions.push(oneDark);
}
if (placeholder) {
extensions.push(cmPlaceholder(placeholder));
}
onMount(() => {
view = new EditorView({
state: EditorState.create({ doc: value, extensions }),
state: EditorState.create({ doc: value, extensions: buildExtensions(theme.isDark) }),
parent: container,
});
@@ -127,31 +124,8 @@
const currentDoc = view.state.doc.toString();
view.destroy();
const extensions = [
jinjaLang,
errorLineField,
EditorView.updateListener.of((update) => {
if (update.docChanged) {
onchange(update.state.doc.toString());
}
}),
EditorView.lineWrapping,
EditorView.theme({
'&': { fontSize: '13px', fontFamily: "'Consolas', 'Monaco', 'Courier New', monospace" },
'.cm-content': { minHeight: `${rows * 1.5}em`, padding: '8px' },
'.cm-editor': { borderRadius: '0.375rem', border: '1px solid var(--color-border)' },
'.cm-focused': { outline: '2px solid var(--color-primary)', outlineOffset: '0px' },
'.cm-error-line': { backgroundColor: 'rgba(239, 68, 68, 0.2)', outline: '1px solid rgba(239, 68, 68, 0.4)' },
'.ͼc': { color: '#e879f9' },
'.ͼd': { color: '#38bdf8' },
'.ͼ5': { color: '#6b7280' },
}),
];
if (isDark) extensions.push(oneDark);
if (placeholder) extensions.push(cmPlaceholder(placeholder));
view = new EditorView({
state: EditorState.create({ doc: currentDoc, extensions }),
state: EditorState.create({ doc: currentDoc, extensions: buildExtensions(isDark) }),
parent: container,
});
}
+83 -11
View File
@@ -12,6 +12,7 @@
"telegramBots": "Bots",
"targets": "Targets",
"users": "Users",
"settings": "Settings",
"logout": "Logout"
},
"auth": {
@@ -62,6 +63,7 @@
"assets": "assets",
"eventActivity": "Event Activity",
"last14days": "Last 14 days",
"event": "event",
"events": "events",
"noChartData": "No event data yet"
},
@@ -85,7 +87,11 @@
"checking": "Checking...",
"loadError": "Failed to load providers.",
"externalDomain": "External Domain",
"optional": "optional"
"optional": "optional",
"urlApiKeyRequired": "URL and API Key are required",
"externalDomainHint": "Public-facing URL for notification links. Falls back to server URL.",
"testAndSave": "Test & Save",
"saveWithoutTest": "Save without testing"
},
"trackers": {
"title": "Trackers",
@@ -134,7 +140,16 @@
"testBasic": "Send test message",
"testPeriodic": "Test periodic summary",
"testScheduled": "Test scheduled assets",
"testMemory": "Test memory / On This Day"
"testMemory": "Test memory / On This Day",
"checkingLinks": "Checking links...",
"missingLinksTitle": "Albums Missing Public Links",
"missingLinksDesc": "The following albums don't have public shared links. Without links, notification recipients won't be able to view photos.",
"expired": "Expired",
"passwordProtected": "Password Protected",
"noLink": "No Link",
"saveWithoutLinks": "Save without links",
"createLinks": "Create {count} link(s)",
"linksNote": "You can also create links manually in Immich."
},
"templates": {
"title": "Templates",
@@ -198,7 +213,8 @@
"create": "Create User",
"delete": "Delete",
"confirmDelete": "Delete this user?",
"joined": "joined"
"joined": "joined",
"noUsers": "No users found"
},
"telegramBot": {
"title": "Telegram Bots",
@@ -220,21 +236,48 @@
"channel": "Channel",
"confirmDelete": "Delete this bot?",
"commands": "Commands",
"enabledCommands": "Enabled Commands",
"defaultCount": "Default result count",
"enabledCommands": "Enabled commands",
"defaultCount": "Default count",
"responseMode": "Response mode",
"modeMedia": "Media (send photos)",
"modeText": "Text (send links)",
"modeMedia": "Media (photos)",
"modeText": "Text only",
"botLocale": "Bot language",
"rateLimits": "Rate Limits",
"rateSearch": "Search cooldown",
"rateFind": "Find cooldown",
"rateDefault": "Default cooldown",
"syncCommands": "Sync to Telegram",
"syncCommands": "Sync with Telegram",
"discoverChats": "Discover chats from Telegram",
"clickToCopy": "Click to copy chat ID",
"chatsDiscovered": "Chats discovered",
"chatDeleted": "Chat removed"
"chatDeleted": "Chat removed",
"cmdLocale": "Bot language",
"searchCooldown": "Search cooldown (s)",
"saveConfig": "Save config",
"commandsSynced": "Commands synced with Telegram",
"registerWebhook": "Register webhook",
"unregisterWebhook": "Unregister webhook",
"webhookRegistered": "Webhook registered",
"webhookUnregistered": "Webhook unregistered",
"updateMode": "Update mode",
"polling": "Polling",
"webhook": "Webhook",
"webhookStatus": "Webhook status",
"webhookActive": "Webhook active",
"webhookNotSet": "No webhook set",
"webhookVerified": "Webhook verified",
"webhookError": "Last error",
"pendingUpdates": "pending updates",
"pollingActive": "Polling active",
"telegramSettings": "Telegram Settings",
"externalUrl": "External URL",
"externalUrlHint": "Public URL of this Notify Bridge instance. Required for webhook mode.",
"webhookSecret": "Webhook secret",
"webhookSecretHint": "Optional secret token to verify webhook requests from Telegram",
"cacheTtl": "Media cache TTL (hours)",
"cacheTtlHint": "How long to cache uploaded Telegram file_ids before re-uploading (default: 48h)",
"settingsSaved": "Settings saved",
"noExternalDomain": "External domain URL not configured"
},
"trackingConfig": {
"title": "Tracking Configs",
@@ -269,6 +312,9 @@
"assetType": "Asset type",
"minRating": "Min rating",
"memoryMode": "Memory Mode (On This Day)",
"memorySource": "Memory source",
"memorySourceAlbums": "Scan tracked albums",
"memorySourceNative": "Immich native memories",
"test": "Test",
"confirmDelete": "Delete this tracking config?",
"sortNone": "None",
@@ -282,7 +328,14 @@
"albumModeRandom": "Random",
"assetTypeAll": "All",
"assetTypePhoto": "Photo",
"assetTypeVideo": "Video"
"assetTypeVideo": "Video",
"periodic": "periodic",
"scheduled": "scheduled",
"memory": "memory",
"added": "added",
"removed": "removed",
"renamed": "renamed",
"deleted": "deleted"
},
"templateConfig": {
"title": "Template Configs",
@@ -324,7 +377,8 @@
"variables": "Variables",
"assetFields": "Asset fields (in {% for asset in added_assets %})",
"albumFields": "Album fields (in {% for album in albums %})",
"confirmDelete": "Delete this template config?"
"confirmDelete": "Delete this template config?",
"invalidFormat": "Invalid format string"
},
"templateVars": {
"message_assets_added": { "description": "Notification when new assets are added to an album" },
@@ -378,10 +432,24 @@
"album_url_field": "Album share URL",
"album_shared": "Whether album is shared"
},
"settings": {
"title": "Settings",
"description": "Global application settings",
"general": "General",
"externalUrl": "External URL",
"externalUrlHint": "Public URL of this Notify Bridge instance (e.g. https://notify.example.com)",
"telegram": "Telegram",
"webhookSecret": "Webhook Secret",
"webhookSecretHint": "Secret token to verify webhook requests from Telegram",
"cacheTtl": "Media Cache TTL (hours)",
"cacheTtlHint": "How long to cache uploaded Telegram file_ids before re-uploading",
"saved": "Settings saved"
},
"hints": {
"periodicSummary": "Sends a scheduled summary of all tracked albums at specified times. Great for daily/weekly digests.",
"scheduledAssets": "Sends random or selected photos from tracked albums on a schedule. Like a daily photo pick.",
"memoryMode": "\"On This Day\" — sends photos taken on this date in previous years. Nostalgic flashbacks.",
"memorySource": "Albums: scans tracked albums for date-matching assets. Native: uses Immich's built-in memories (covers entire library, optionally filtered by tracked albums).",
"favoritesOnly": "Only include assets marked as favorites.",
"maxAssets": "Maximum number of asset details to include in a single notification message.",
"periodicStartDate": "The reference date for calculating periodic intervals. Summaries are sent every N days from this date.",
@@ -406,6 +474,10 @@
"botLocale": "Language for command descriptions in Telegram's menu and bot response messages.",
"rateLimits": "Cooldown in seconds between uses of each command category per chat. 0 = no limit."
},
"snackbar": {
"showDetails": "Show details",
"hideDetails": "Hide details"
},
"snack": {
"providerSaved": "Provider saved",
"providerDeleted": "Provider deleted",
+81 -9
View File
@@ -12,6 +12,7 @@
"telegramBots": "Боты",
"targets": "Получатели",
"users": "Пользователи",
"settings": "Настройки",
"logout": "Выход"
},
"auth": {
@@ -62,6 +63,7 @@
"assets": "файлов",
"eventActivity": "Активность событий",
"last14days": "Последние 14 дней",
"event": "событие",
"events": "событий",
"noChartData": "Нет данных о событиях"
},
@@ -85,7 +87,11 @@
"checking": "Проверка...",
"loadError": "Не удалось загрузить провайдеры.",
"externalDomain": "Внешний домен",
"optional": "необязательно"
"optional": "необязательно",
"urlApiKeyRequired": "URL и API ключ обязательны",
"externalDomainHint": "Публичный URL для ссылок в уведомлениях. По умолчанию используется URL сервера.",
"testAndSave": "Проверить и сохранить",
"saveWithoutTest": "Сохранить без проверки"
},
"trackers": {
"title": "Трекеры",
@@ -134,7 +140,16 @@
"testBasic": "Отправить тестовое сообщение",
"testPeriodic": "Тест периодической сводки",
"testScheduled": "Тест запланированных фото",
"testMemory": "Тест воспоминаний"
"testMemory": "Тест воспоминаний",
"checkingLinks": "Проверка ссылок...",
"missingLinksTitle": "Альбомы без публичных ссылок",
"missingLinksDesc": "У следующих альбомов нет публичных ссылок. Без ссылок получатели уведомлений не смогут просматривать фото.",
"expired": "Истёк",
"passwordProtected": "Защищён паролем",
"noLink": "Нет ссылки",
"saveWithoutLinks": "Сохранить без ссылок",
"createLinks": "Создать {count} ссылку(и)",
"linksNote": "Вы также можете создать ссылки вручную в Immich."
},
"templates": {
"title": "Шаблоны",
@@ -198,7 +213,8 @@
"create": "Создать",
"delete": "Удалить",
"confirmDelete": "Удалить этого пользователя?",
"joined": "зарегистрирован"
"joined": "зарегистрирован",
"noUsers": "Пользователи не найдены"
},
"telegramBot": {
"title": "Telegram боты",
@@ -221,10 +237,10 @@
"confirmDelete": "Удалить этого бота?",
"commands": "Команды",
"enabledCommands": "Включённые команды",
"defaultCount": "Кол-во результатов",
"defaultCount": "Кол-во по умолчанию",
"responseMode": "Режим ответа",
"modeMedia": "Медиа (отправка фото)",
"modeText": "Текст (ссылки)",
"modeMedia": "Медиа (фото)",
"modeText": "Только текст",
"botLocale": "Язык бота",
"rateLimits": "Ограничения частоты",
"rateSearch": "Кулдаун поиска",
@@ -234,7 +250,34 @@
"discoverChats": "Обнаружить чаты из Telegram",
"clickToCopy": "Нажмите, чтобы скопировать ID чата",
"chatsDiscovered": "Чаты обнаружены",
"chatDeleted": "Чат удалён"
"chatDeleted": "Чат удалён",
"cmdLocale": "Язык бота",
"searchCooldown": "Кулдаун поиска (с)",
"saveConfig": "Сохранить настройки",
"commandsSynced": "Команды синхронизированы с Telegram",
"registerWebhook": "Зарегистрировать вебхук",
"unregisterWebhook": "Удалить вебхук",
"webhookRegistered": "Вебхук зарегистрирован",
"webhookUnregistered": "Вебхук удалён",
"updateMode": "Режим обновлений",
"polling": "Опрос",
"webhook": "Вебхук",
"webhookStatus": "Статус вебхука",
"webhookActive": "Вебхук активен",
"webhookNotSet": "Вебхук не установлен",
"webhookVerified": "Вебхук проверен",
"webhookError": "Последняя ошибка",
"pendingUpdates": "ожидающих обновлений",
"pollingActive": "Опрос активен",
"telegramSettings": "Настройки Telegram",
"externalUrl": "Внешний URL",
"externalUrlHint": "Публичный URL этого экземпляра Notify Bridge. Необходим для режима вебхука.",
"webhookSecret": "Секрет вебхука",
"webhookSecretHint": "Секретный токен для проверки запросов вебхука от Telegram (необязательно)",
"cacheTtl": "TTL кэша медиа (часы)",
"cacheTtlHint": "Сколько хранить кэш Telegram file_id перед повторной загрузкой (по умолчанию: 48ч)",
"settingsSaved": "Настройки сохранены",
"noExternalDomain": "Внешний URL домена не настроен"
},
"trackingConfig": {
"title": "Конфигурации отслеживания",
@@ -269,6 +312,9 @@
"assetType": "Тип файлов",
"minRating": "Мин. рейтинг",
"memoryMode": "Воспоминания (В этот день)",
"memorySource": "Источник воспоминаний",
"memorySourceAlbums": "Сканировать альбомы",
"memorySourceNative": "Встроенные воспоминания Immich",
"test": "Тест",
"confirmDelete": "Удалить эту конфигурацию отслеживания?",
"sortNone": "Нет",
@@ -282,7 +328,14 @@
"albumModeRandom": "Случайный",
"assetTypeAll": "Все",
"assetTypePhoto": "Фото",
"assetTypeVideo": "Видео"
"assetTypeVideo": "Видео",
"periodic": "периодический",
"scheduled": "запланированный",
"memory": "воспоминания",
"added": "добавление",
"removed": "удаление",
"renamed": "переименование",
"deleted": "удалён"
},
"templateConfig": {
"title": "Конфигурации шаблонов",
@@ -324,7 +377,8 @@
"variables": "Переменные",
"assetFields": "Поля файла (в {% for asset in added_assets %})",
"albumFields": "Поля альбома (в {% for album in albums %})",
"confirmDelete": "Удалить эту конфигурацию шаблона?"
"confirmDelete": "Удалить эту конфигурацию шаблона?",
"invalidFormat": "Некорректная строка формата"
},
"templateVars": {
"message_assets_added": { "description": "Уведомление о добавлении файлов в альбом" },
@@ -378,10 +432,24 @@
"album_url_field": "Ссылка на альбом",
"album_shared": "Общий альбом"
},
"settings": {
"title": "Настройки",
"description": "Глобальные настройки приложения",
"general": "Общие",
"externalUrl": "Внешний URL",
"externalUrlHint": "Публичный URL этого экземпляра Notify Bridge (напр. https://notify.example.com)",
"telegram": "Telegram",
"webhookSecret": "Секрет вебхука",
"webhookSecretHint": "Секретный токен для проверки запросов вебхука от Telegram",
"cacheTtl": "TTL кэша медиа (часы)",
"cacheTtlHint": "Сколько хранить кэш Telegram file_id перед повторной загрузкой",
"saved": "Настройки сохранены"
},
"hints": {
"periodicSummary": "Отправляет плановую сводку по всем отслеживаемым альбомам в указанное время. Подходит для ежедневных/еженедельных дайджестов.",
"scheduledAssets": "Отправляет случайные или выбранные фото из альбомов по расписанию. Как ежедневная подборка фото.",
"memoryMode": "\"В этот день\" — отправляет фото, сделанные в этот день в прошлые годы. Ностальгические воспоминания.",
"memorySource": "Альбомы: сканирует отслеживаемые альбомы по дате. Встроенные: использует воспоминания Immich (вся библиотека, с фильтрацией по альбомам).",
"favoritesOnly": "Включать только ассеты, отмеченные как избранные.",
"maxAssets": "Максимальное количество ассетов в одном уведомлении.",
"periodicStartDate": "Опорная дата для расчёта интервалов. Сводки отправляются каждые N дней от этой даты.",
@@ -406,6 +474,10 @@
"botLocale": "Язык описаний команд в меню Telegram и ответов бота.",
"rateLimits": "Кулдаун в секундах между использованиями команд в каждом чате. 0 = без ограничений."
},
"snackbar": {
"showDetails": "Показать детали",
"hideDetails": "Скрыть детали"
},
"snack": {
"providerSaved": "Провайдер сохранён",
"providerDeleted": "Провайдер удалён",