diff --git a/docs/using-overseerr/notifications/telegram.md b/docs/using-overseerr/notifications/telegram.md index 9bdb96dbc3..5efb536dfc 100644 --- a/docs/using-overseerr/notifications/telegram.md +++ b/docs/using-overseerr/notifications/telegram.md @@ -28,6 +28,10 @@ At the end of the bot creation process, [@BotFather](https://telegram.me/botfath To obtain your chat ID, simply create a new group chat, add [@get_id_bot](https://telegram.me/get_id_bot), and issue the `/my_id` command. +### Message Thread ID (optional) + +Optionally send messages to a specific thread. To obtain your message thread ID, you must have threads enabled on your group. Click on the thread you want and copy the last number after the last '/' in the url. + ### Send Silently (optional) Optionally, notifications can be sent silently. Silent notifications send messages without notification sounds. diff --git a/docs/using-overseerr/notifications/webhooks.md b/docs/using-overseerr/notifications/webhooks.md index cd2ada314f..21e42d503c 100644 --- a/docs/using-overseerr/notifications/webhooks.md +++ b/docs/using-overseerr/notifications/webhooks.md @@ -36,13 +36,14 @@ Customize the JSON payload to suit your needs. Overseerr provides several [templ These variables are for the target recipient of the notification. -| Variable | Value | -| ---------------------------------------- | ------------------------------------------------------------- | -| `{{notifyuser_username}}` | The target notification recipient's username | -| `{{notifyuser_email}}` | The target notification recipient's email address | -| `{{notifyuser_avatar}}` | The target notification recipient's avatar URL | -| `{{notifyuser_settings_discordId}}` | The target notification recipient's Discord ID (if set) | -| `{{notifyuser_settings_telegramChatId}}` | The target notification recipient's Telegram Chat ID (if set) | +| Variable | Value | +| ------------------------------------------------- | ----------------------------------------------------------------------- | +| `{{notifyuser_username}}` | The target notification recipient's username | +| `{{notifyuser_email}}` | The target notification recipient's email address | +| `{{notifyuser_avatar}}` | The target notification recipient's avatar URL | +| `{{notifyuser_settings_discordId}}` | The target notification recipient's Discord ID (if set) | +| `{{notifyuser_settings_telegramChatId}}` | The target notification recipient's Telegram Chat ID (if set) | +| `{{notifyuser_settings_telegramMessageThreadId}}` | The target notification recipient's Telegram Message Thread ID (if set) | {% hint style="info" %} The `notifyuser` variables are not defined for the following request notification types, as they are intended for application administrators rather than end users: diff --git a/server/entity/UserSettings.ts b/server/entity/UserSettings.ts index ea4a7d33bf..0e8ca7132b 100644 --- a/server/entity/UserSettings.ts +++ b/server/entity/UserSettings.ts @@ -60,6 +60,9 @@ export class UserSettings { @Column({ nullable: true }) public telegramSendSilently?: boolean; + @Column({ nullable: true }) + public telegramMessageThreadId?: string; + @Column({ nullable: true }) public watchlistSyncMovies?: boolean; diff --git a/server/interfaces/api/userSettingsInterfaces.ts b/server/interfaces/api/userSettingsInterfaces.ts index fb0767b211..da2d9ba31f 100644 --- a/server/interfaces/api/userSettingsInterfaces.ts +++ b/server/interfaces/api/userSettingsInterfaces.ts @@ -33,6 +33,7 @@ export interface UserSettingsNotificationsResponse { telegramBotUsername?: string; telegramChatId?: string; telegramSendSilently?: boolean; + telegramMessageThreadId?: string; webPushEnabled?: boolean; notificationTypes: Partial; } diff --git a/server/lib/notifications/agents/telegram.ts b/server/lib/notifications/agents/telegram.ts index 7d7062122c..e7b8c521d0 100644 --- a/server/lib/notifications/agents/telegram.ts +++ b/server/lib/notifications/agents/telegram.ts @@ -19,6 +19,7 @@ interface TelegramMessagePayload { parse_mode: string; chat_id: string; disable_notification: boolean; + message_thread_id?: number; } interface TelegramPhotoPayload { @@ -27,6 +28,7 @@ interface TelegramPhotoPayload { parse_mode: string; chat_id: string; disable_notification: boolean; + message_thread_id?: number; } class TelegramAgent @@ -179,6 +181,11 @@ class TelegramAgent ...notificationPayload, chat_id: settings.options.chatId, disable_notification: !!settings.options.sendSilently, + ...(settings.options.messageThreadId + ? { + message_thread_id: Number(settings.options.messageThreadId), + } + : {}), } as TelegramMessagePayload | TelegramPhotoPayload); } catch (e) { logger.error('Error sending Telegram notification', { @@ -215,6 +222,13 @@ class TelegramAgent chat_id: payload.notifyUser.settings.telegramChatId, disable_notification: !!payload.notifyUser.settings.telegramSendSilently, + ...(payload.notifyUser.settings.telegramMessageThreadId + ? { + message_thread_id: Number( + payload.notifyUser.settings.telegramMessageThreadId + ), + } + : {}), } as TelegramMessagePayload | TelegramPhotoPayload); } catch (e) { logger.error('Error sending Telegram notification', { @@ -261,6 +275,13 @@ class TelegramAgent ...notificationPayload, chat_id: user.settings.telegramChatId, disable_notification: !!user.settings?.telegramSendSilently, + ...(user.settings?.telegramMessageThreadId + ? { + message_thread_id: Number( + user.settings.telegramMessageThreadId + ), + } + : {}), } as TelegramMessagePayload | TelegramPhotoPayload); } catch (e) { logger.error('Error sending Telegram notification', { diff --git a/server/lib/settings.ts b/server/lib/settings.ts index 63daf17f36..1e07f1b7a9 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -178,6 +178,12 @@ export interface NotificationAgentTelegram extends NotificationAgentConfig { botAPI: string; chatId: string; sendSilently: boolean; + /** + * Optional Telegram message thread ID (for discussion topics in supergroups) + * If provided, Overseerr will send all system notifications to + * the specified thread within the configured chat. + */ + messageThreadId?: string; }; } @@ -359,6 +365,7 @@ class Settings { botAPI: '', chatId: '', sendSilently: false, + messageThreadId: '', }, }, pushbullet: { diff --git a/server/routes/user/usersettings.ts b/server/routes/user/usersettings.ts index c8b3f50bd2..82607b0369 100644 --- a/server/routes/user/usersettings.ts +++ b/server/routes/user/usersettings.ts @@ -278,6 +278,7 @@ userSettingsRoutes.get<{ id: string }, UserSettingsNotificationsResponse>( telegramEnabled: settings.telegram.enabled, telegramBotUsername: settings.telegram.options.botUsername, telegramChatId: user.settings?.telegramChatId, + telegramMessageThreadId: user.settings?.telegramMessageThreadId, telegramSendSilently: user.settings?.telegramSendSilently, webPushEnabled: settings.webpush.enabled, notificationTypes: user.settings?.notificationTypes ?? {}, @@ -320,6 +321,7 @@ userSettingsRoutes.post<{ id: string }, UserSettingsNotificationsResponse>( pushoverApplicationToken: req.body.pushoverApplicationToken, pushoverUserKey: req.body.pushoverUserKey, telegramChatId: req.body.telegramChatId, + telegramMessageThreadId: req.body.telegramMessageThreadId, telegramSendSilently: req.body.telegramSendSilently, notificationTypes: req.body.notificationTypes, }); @@ -332,6 +334,8 @@ userSettingsRoutes.post<{ id: string }, UserSettingsNotificationsResponse>( user.settings.pushoverUserKey = req.body.pushoverUserKey; user.settings.pushoverSound = req.body.pushoverSound; user.settings.telegramChatId = req.body.telegramChatId; + user.settings.telegramMessageThreadId = + req.body.telegramMessageThreadId; user.settings.telegramSendSilently = req.body.telegramSendSilently; user.settings.notificationTypes = Object.assign( {}, @@ -350,6 +354,7 @@ userSettingsRoutes.post<{ id: string }, UserSettingsNotificationsResponse>( pushoverUserKey: user.settings.pushoverUserKey, pushoverSound: user.settings.pushoverSound, telegramChatId: user.settings.telegramChatId, + telegramMessageThreadId: user.settings.telegramMessageThreadId, telegramSendSilently: user.settings.telegramSendSilently, notificationTypes: user.settings.notificationTypes, }); diff --git a/src/components/Settings/Notifications/NotificationsTelegram.tsx b/src/components/Settings/Notifications/NotificationsTelegram.tsx index 292051ca24..7b2d5b9375 100644 --- a/src/components/Settings/Notifications/NotificationsTelegram.tsx +++ b/src/components/Settings/Notifications/NotificationsTelegram.tsx @@ -32,6 +32,8 @@ const messages = defineMessages({ toastTelegramTestFailed: 'Telegram test notification failed to send.', sendSilently: 'Send Silently', sendSilentlyTip: 'Send notifications with no sound', + messageThreadId: 'Message Thread ID', + messageThreadIdTip: 'Optional message thread ID', }); const NotificationsTelegram = () => { @@ -64,6 +66,9 @@ const NotificationsTelegram = () => { /^-?\d+$/, intl.formatMessage(messages.validationChatIdRequired) ), + messageThreadId: Yup.string() + .nullable() + .matches(/^\d*$/, intl.formatMessage(messages.validationChatIdRequired)), }); if (!data && !error) { @@ -79,6 +84,7 @@ const NotificationsTelegram = () => { botAPI: data?.options.botAPI, chatId: data?.options.chatId, sendSilently: data?.options.sendSilently, + messageThreadId: data?.options.messageThreadId, }} validationSchema={NotificationsTelegramSchema} onSubmit={async (values) => { @@ -91,6 +97,7 @@ const NotificationsTelegram = () => { chatId: values.chatId, sendSilently: values.sendSilently, botUsername: values.botUsername, + messageThreadId: values.messageThreadId, }, }); @@ -139,6 +146,7 @@ const NotificationsTelegram = () => { chatId: values.chatId, sendSilently: values.sendSilently, botUsername: values.botUsername, + messageThreadId: values.messageThreadId, }, }); @@ -285,6 +293,32 @@ const NotificationsTelegram = () => { )} +
+ +
+
+ +
+ {errors.messageThreadId && + touched.messageThreadId && + typeof errors.messageThreadId === 'string' && ( +
{errors.messageThreadId}
+ )} +
+
+
+ +
+
+ +
+ {errors.telegramMessageThreadId && + touched.telegramMessageThreadId && + typeof errors.telegramMessageThreadId === 'string' && ( +
+ {errors.telegramMessageThreadId} +
+ )} +
+