From 5fb2f4df4359a422653a39eb79f85ace803106e0 Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Sun, 14 Jun 2026 18:56:04 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=BA=D1=81=D0=B8=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=A2=D0=B5=D0=BB=D0=B5=D0=B3=D1=80=D0=B0=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/jellybit/serve.go | 31 ++++++++++++++++++++++++++++--- internal/config/config.go | 1 + internal/metadata/http.go | 7 ++++++- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/cmd/jellybit/serve.go b/cmd/jellybit/serve.go index 8ad108f..995c11b 100644 --- a/cmd/jellybit/serve.go +++ b/cmd/jellybit/serve.go @@ -6,6 +6,7 @@ import ( "flag" "fmt" "net/http" + "net/url" "os/signal" "syscall" "time" @@ -135,7 +136,11 @@ func runServe(args []string) error { if cfg.Telegram.Token == "" { return fmt.Errorf("telegram enabled, but token is empty") } - api, terr := tgbotapi.NewBotAPI(cfg.Telegram.Token) + tgClient, perr := telegramHTTPClient(cfg.Telegram.Proxy) + if perr != nil { + return perr + } + api, terr := tgbotapi.NewBotAPIWithClient(cfg.Telegram.Token, tgbotapi.APIEndpoint, tgClient) if terr != nil { logger.Error("telegram bot disabled: cannot connect", "err", terr) } else { @@ -182,6 +187,24 @@ func runServe(args []string) error { return nil } +// telegramHTTPClient собирает HTTP-клиент бота с опц. прокси. Таймаута уровня +// клиента нет намеренно — он порвал бы long-poll; вместо этого ограничиваем +// установление соединения (dial/TLS из DefaultTransport) и ожидание заголовков +// ответа с запасом над long-poll (30с в tgbot). Так мёртвый прокси не подвешивает +// ни отправку уведомлений, ни приёмный цикл навсегда — клиент переподключится. +func telegramHTTPClient(proxy string) (*http.Client, error) { + transport := http.DefaultTransport.(*http.Transport).Clone() + if proxy != "" { + proxyURL, err := url.Parse(proxy) + if err != nil { + return nil, fmt.Errorf("telegram: parse proxy %q: %w", proxy, err) + } + transport.Proxy = http.ProxyURL(proxyURL) + } + transport.ResponseHeaderTimeout = 45 * time.Second + return &http.Client{Transport: transport}, nil +} + // metadataProviders собирает включённые конфигом базы метаданных. Для // сериалов Jellyfin привычнее tvdbid, поэтому TVDB идёт первым. func metadataProviders(cfg *config.Config) ([]metadata.Provider, error) { @@ -197,7 +220,9 @@ func metadataProviders(cfg *config.Config) ([]metadata.Provider, error) { } out = append(out, p) } - if cfg.Metadata.TVDB.Enabled { + // TVDB/TMDB включаются ключом: если enabled, но ключ пуст — тихо + // пропускаем (сервис стартует), а не падаем. + if cfg.Metadata.TVDB.Enabled && cfg.Metadata.TVDB.APIKey != "" { p, err := metadata.NewTVDB(metadata.TVDBConfig{ APIKey: cfg.Metadata.TVDB.APIKey, Proxy: cfg.Metadata.TVDB.Proxy, @@ -208,7 +233,7 @@ func metadataProviders(cfg *config.Config) ([]metadata.Provider, error) { } out = append(out, p) } - if cfg.Metadata.TMDB.Enabled { + if cfg.Metadata.TMDB.Enabled && cfg.Metadata.TMDB.APIKey != "" { p, err := metadata.NewTMDB(metadata.TMDBConfig{ APIKey: cfg.Metadata.TMDB.APIKey, Proxy: cfg.Metadata.TMDB.Proxy, diff --git a/internal/config/config.go b/internal/config/config.go index 1d46026..d915580 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -96,6 +96,7 @@ type Telegram struct { Token string `toml:"token"` AllowedUserIDs []int64 `toml:"allowed_user_ids"` WebBaseURL string `toml:"web_base_url"` // для deep-link «открыть в вебе» (опц.) + Proxy string `toml:"proxy"` // опц. HTTP-прокси для api.telegram.org } // HTTP — параметры веб-сервера. diff --git a/internal/metadata/http.go b/internal/metadata/http.go index 8561071..44609ed 100644 --- a/internal/metadata/http.go +++ b/internal/metadata/http.go @@ -24,7 +24,12 @@ func newHTTPClient(proxy string, timeout time.Duration) (*http.Client, error) { if err != nil { return nil, fmt.Errorf("metadata: parse proxy %q: %w", proxy, err) } - transport = &http.Transport{Proxy: http.ProxyURL(u)} + // Клонируем дефолтный транспорт (dial/TLS-таймауты, keep-alive), а не + // собираем голый — иначе при живом-но-залипшем прокси полагались бы + // только на общий Client.Timeout. Он остаётся верхней границей запроса. + t := http.DefaultTransport.(*http.Transport).Clone() + t.Proxy = http.ProxyURL(u) + transport = t } return &http.Client{Timeout: timeout, Transport: transport}, nil }