diff --git a/app/client/api-client/types.gen.ts b/app/client/api-client/types.gen.ts index 058f023..7fb6d77 100644 --- a/app/client/api-client/types.gen.ts +++ b/app/client/api-client/types.gen.ts @@ -1990,6 +1990,10 @@ export type GetScheduleNotificationsResponses = { } | { shoutrrrUrl: string; type: 'custom'; + } | { + type: 'telegram'; + botToken: string; + chatId: string; } | { type: 'discord'; webhookUrl: string; @@ -2007,7 +2011,7 @@ export type GetScheduleNotificationsResponses = { enabled: boolean; id: number; name: string; - type: 'custom' | 'discord' | 'email' | 'gotify' | 'ntfy' | 'pushover' | 'slack'; + type: 'custom' | 'discord' | 'email' | 'gotify' | 'ntfy' | 'pushover' | 'slack' | 'telegram'; updatedAt: number; }; destinationId: number; @@ -2074,6 +2078,10 @@ export type UpdateScheduleNotificationsResponses = { } | { shoutrrrUrl: string; type: 'custom'; + } | { + type: 'telegram'; + botToken: string; + chatId: string; } | { type: 'discord'; webhookUrl: string; @@ -2091,7 +2099,7 @@ export type UpdateScheduleNotificationsResponses = { enabled: boolean; id: number; name: string; - type: 'custom' | 'discord' | 'email' | 'gotify' | 'ntfy' | 'pushover' | 'slack'; + type: 'custom' | 'discord' | 'email' | 'gotify' | 'ntfy' | 'pushover' | 'slack' | 'telegram'; updatedAt: number; }; destinationId: number; @@ -2147,6 +2155,10 @@ export type ListNotificationDestinationsResponses = { } | { shoutrrrUrl: string; type: 'custom'; + } | { + type: 'telegram'; + botToken: string; + chatId: string; } | { type: 'discord'; webhookUrl: string; @@ -2164,7 +2176,7 @@ export type ListNotificationDestinationsResponses = { enabled: boolean; id: number; name: string; - type: 'custom' | 'discord' | 'email' | 'gotify' | 'ntfy' | 'pushover' | 'slack'; + type: 'custom' | 'discord' | 'email' | 'gotify' | 'ntfy' | 'pushover' | 'slack' | 'telegram'; updatedAt: number; }>; }; @@ -2204,6 +2216,10 @@ export type CreateNotificationDestinationData = { } | { shoutrrrUrl: string; type: 'custom'; + } | { + type: 'telegram'; + botToken: string; + chatId: string; } | { type: 'discord'; webhookUrl: string; @@ -2260,6 +2276,10 @@ export type CreateNotificationDestinationResponses = { } | { shoutrrrUrl: string; type: 'custom'; + } | { + type: 'telegram'; + botToken: string; + chatId: string; } | { type: 'discord'; webhookUrl: string; @@ -2277,7 +2297,7 @@ export type CreateNotificationDestinationResponses = { enabled: boolean; id: number; name: string; - type: 'custom' | 'discord' | 'email' | 'gotify' | 'ntfy' | 'pushover' | 'slack'; + type: 'custom' | 'discord' | 'email' | 'gotify' | 'ntfy' | 'pushover' | 'slack' | 'telegram'; updatedAt: number; }; }; @@ -2363,6 +2383,10 @@ export type GetNotificationDestinationResponses = { } | { shoutrrrUrl: string; type: 'custom'; + } | { + type: 'telegram'; + botToken: string; + chatId: string; } | { type: 'discord'; webhookUrl: string; @@ -2380,7 +2404,7 @@ export type GetNotificationDestinationResponses = { enabled: boolean; id: number; name: string; - type: 'custom' | 'discord' | 'email' | 'gotify' | 'ntfy' | 'pushover' | 'slack'; + type: 'custom' | 'discord' | 'email' | 'gotify' | 'ntfy' | 'pushover' | 'slack' | 'telegram'; updatedAt: number; }; }; @@ -2420,6 +2444,10 @@ export type UpdateNotificationDestinationData = { } | { shoutrrrUrl: string; type: 'custom'; + } | { + type: 'telegram'; + botToken: string; + chatId: string; } | { type: 'discord'; webhookUrl: string; @@ -2486,6 +2514,10 @@ export type UpdateNotificationDestinationResponses = { } | { shoutrrrUrl: string; type: 'custom'; + } | { + type: 'telegram'; + botToken: string; + chatId: string; } | { type: 'discord'; webhookUrl: string; @@ -2503,7 +2535,7 @@ export type UpdateNotificationDestinationResponses = { enabled: boolean; id: number; name: string; - type: 'custom' | 'discord' | 'email' | 'gotify' | 'ntfy' | 'pushover' | 'slack'; + type: 'custom' | 'discord' | 'email' | 'gotify' | 'ntfy' | 'pushover' | 'slack' | 'telegram'; updatedAt: number; }; }; diff --git a/app/client/modules/notifications/components/create-notification-form.tsx b/app/client/modules/notifications/components/create-notification-form.tsx index ee05948..c92321e 100644 --- a/app/client/modules/notifications/components/create-notification-form.tsx +++ b/app/client/modules/notifications/components/create-notification-form.tsx @@ -70,6 +70,11 @@ const defaultValuesForType = { apiToken: "", priority: 0 as const, }, + telegram: { + type: "telegram" as const, + botToken: "", + chatId: "" + }, custom: { type: "custom" as const, shoutrrrUrl: "", @@ -146,6 +151,7 @@ export const CreateNotificationForm = ({ onSubmit, mode = "create", initialValue Gotify Ntfy Pushover + Telegram Custom (Shoutrrr URL) @@ -613,6 +619,43 @@ export const CreateNotificationForm = ({ onSubmit, mode = "create", initialValue )} + {watchedType === "telegram" && ( + <> + ( + + Bot Token + + + + + Telegram bot token. Get this from BotFather when you create your bot. + + + + )} + /> + ( + + Chat ID + + + + + Telegram chat ID to send notifications to. + + + + )} + /> + + )} + {watchedType === "custom" && ( Gotify Ntfy Pushover + Telegram Custom diff --git a/app/schemas/notifications.ts b/app/schemas/notifications.ts index 33bafcc..28ff545 100644 --- a/app/schemas/notifications.ts +++ b/app/schemas/notifications.ts @@ -7,6 +7,7 @@ export const NOTIFICATION_TYPES = { gotify: "gotify", ntfy: "ntfy", pushover: "pushover", + telegram: "telegram", custom: "custom", } as const; @@ -64,6 +65,12 @@ export const pushoverNotificationConfigSchema = type({ priority: "-1 | 0 | 1", }); +export const telegramNotificationConfigSchema = type({ + type: "'telegram'", + botToken: "string", + chatId: "string", +}); + export const customNotificationConfigSchema = type({ type: "'custom'", shoutrrrUrl: "string", @@ -75,6 +82,7 @@ export const notificationConfigSchema = emailNotificationConfigSchema .or(gotifyNotificationConfigSchema) .or(ntfyNotificationConfigSchema) .or(pushoverNotificationConfigSchema) + .or(telegramNotificationConfigSchema) .or(customNotificationConfigSchema); export type NotificationConfig = typeof notificationConfigSchema.infer; diff --git a/app/server/modules/notifications/builders/index.ts b/app/server/modules/notifications/builders/index.ts index a6e125c..fae5f3b 100644 --- a/app/server/modules/notifications/builders/index.ts +++ b/app/server/modules/notifications/builders/index.ts @@ -5,6 +5,7 @@ import { buildDiscordShoutrrrUrl } from "./discord"; import { buildGotifyShoutrrrUrl } from "./gotify"; import { buildNtfyShoutrrrUrl } from "./ntfy"; import { buildPushoverShoutrrrUrl } from "./pushover"; +import { buildTelegramShoutrrrUrl } from "./telegram"; import { buildCustomShoutrrrUrl } from "./custom"; export function buildShoutrrrUrl(config: NotificationConfig): string { @@ -21,6 +22,8 @@ export function buildShoutrrrUrl(config: NotificationConfig): string { return buildNtfyShoutrrrUrl(config); case "pushover": return buildPushoverShoutrrrUrl(config); + case "telegram": + return buildTelegramShoutrrrUrl(config); case "custom": return buildCustomShoutrrrUrl(config); default: { diff --git a/app/server/modules/notifications/builders/telegram.ts b/app/server/modules/notifications/builders/telegram.ts new file mode 100644 index 0000000..3af34ad --- /dev/null +++ b/app/server/modules/notifications/builders/telegram.ts @@ -0,0 +1,8 @@ +import type { NotificationConfig } from "~/schemas/notifications"; + +export function buildTelegramShoutrrrUrl(config: Extract): string { + // Shoutrrr format: telegram://bottoken@telegram?channels=chatid1,chatid2 + // config: { type: 'telegram', botToken: string, chatId: string } + // Do NOT encode botToken or chatId; Shoutrrr expects raw values (colon, dash, etc.) + return `telegram://${config.botToken}@telegram?channels=${config.chatId}`; +} diff --git a/app/server/modules/notifications/notifications.service.ts b/app/server/modules/notifications/notifications.service.ts index 678dfc0..4099d8b 100644 --- a/app/server/modules/notifications/notifications.service.ts +++ b/app/server/modules/notifications/notifications.service.ts @@ -65,6 +65,11 @@ async function encryptSensitiveFields(config: NotificationConfig): Promise