From 93ff60e56858a2342c6b46545523f7b421510d50 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Wed, 24 Jun 2026 17:34:05 +0200 Subject: [PATCH] fix(tgbot): reload bot on settings save so a new token takes effect without a panel restart The Telegram bot was only started at panel boot, so saving a token or toggling tgBotEnable persisted to the DB but never reached the running bot until a full restart, making it look like the token did not save (issue #5539). The settings/update controller now reconciles the bot the same way panelOutbound reconciles Xray: when tgBotEnable, the token, chat ID, or API server change, it stops/(re)starts the bot and updates the event-bus subscription. --- internal/web/controller/setting.go | 19 +++++++++++++++++++ internal/web/web.go | 22 ++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/internal/web/controller/setting.go b/internal/web/controller/setting.go index 1a85661c7..ecc976301 100644 --- a/internal/web/controller/setting.go +++ b/internal/web/controller/setting.go @@ -88,6 +88,10 @@ func (a *SettingController) updateSetting(c *gin.Context) { } oldTwoFactor, twoFactorErr := a.settingService.GetTwoFactorEnable() oldPanelOutbound, _ := a.settingService.GetPanelOutbound() + oldTgEnable, _ := a.settingService.GetTgbotEnabled() + oldTgToken, _ := a.settingService.GetTgBotToken() + oldTgChatId, _ := a.settingService.GetTgBotChatId() + oldTgAPIServer, _ := a.settingService.GetTgBotAPIServer() err := a.settingService.UpdateAllSetting(allSetting) if err == nil && twoFactorErr == nil && !oldTwoFactor && allSetting.TwoFactorEnable { if bumpErr := a.userService.BumpLoginEpoch(); bumpErr != nil { @@ -102,6 +106,16 @@ func (a *SettingController) updateSetting(c *gin.Context) { logger.Warning("apply panel outbound change failed:", applyErr) } } + // UpdateAllSetting already restored a redacted-blank token, so allSetting.TgBotToken is the effective value to compare. + if err == nil && reloadTgbotFunc != nil { + tgChanged := oldTgEnable != allSetting.TgBotEnable || + (allSetting.TgBotEnable && (oldTgToken != allSetting.TgBotToken || + oldTgChatId != allSetting.TgBotChatId || + oldTgAPIServer != allSetting.TgBotAPIServer)) + if tgChanged { + reloadTgbotFunc() + } + } jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err) } @@ -252,6 +266,11 @@ var testTgFunc func() error // SetTestTgFunc registers the function used to test Telegram sending. func SetTestTgFunc(fn func() error) { testTgFunc = fn } +// reloadTgbotFunc is wired from the web layer; importing tgbot here would be a circular dependency. +var reloadTgbotFunc func() + +func SetReloadTgbotFunc(fn func()) { reloadTgbotFunc = fn } + // emailService is set from web layer. var emailService *email.EmailService diff --git a/internal/web/web.go b/internal/web/web.go index 6d50faafa..13a09c021 100644 --- a/internal/web/web.go +++ b/internal/web/web.go @@ -619,6 +619,28 @@ func (s *Server) start(restartXray bool, startTgBot bool) (err error) { return nil }) + controller.SetReloadTgbotFunc(func() { + enabled, err := s.settingService.GetTgbotEnabled() + if err != nil || !enabled { + if s.tgbotService.IsRunning() { + s.tgbotService.Stop() + } + if s.bus != nil { + s.bus.Unsubscribe("tg-notifier") + } + return + } + // Start() stops any previous receiver first, so it is safe whether or not the bot is already running. + tgBot := s.tgbotService.NewTgbot() + if startErr := tgBot.Start(i18nFS); startErr != nil { + logger.Warning("reload Telegram bot failed:", startErr) + return + } + if s.bus != nil { + s.bus.Subscribe("tg-notifier", s.tgbotService.HandleEvent) + } + }) + s.startTask(restartXray) if startTgBot {