package main import ( "bytes" "context" "encoding/json" "fmt" "io" "log" "net" "net/http" "net/url" "time" "golang.org/x/net/proxy" ) // telegramHTTPClient возвращает цепочку http.Client для отправки запросов к Telegram API. // Порядок попыток: HTTP прокси → SOCKS5 → прямое соединение. // Если предыдущий вариант недоступен — автоматически пробуется следующий. func telegramHTTPClients() []*http.Client { timeout := 15 * time.Second var clients []*http.Client // 1. HTTP прокси if cfgTelegramHTTPProxy != "" { proxyURL, err := url.Parse(cfgTelegramHTTPProxy) if err != nil { log.Printf("[Telegram] ⚠️ Некорректный адрес HTTP прокси %q: %v", cfgTelegramHTTPProxy, err) } else { clients = append(clients, &http.Client{ Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}, Timeout: timeout, }) } } // 2. SOCKS5 прокси if cfgTelegramSocks5Proxy != "" { var auth *proxy.Auth if cfgTelegramSocks5User != "" { auth = &proxy.Auth{ User: cfgTelegramSocks5User, Password: cfgTelegramSocks5Password, } } dialer, err := proxy.SOCKS5("tcp", cfgTelegramSocks5Proxy, auth, proxy.Direct) if err != nil { log.Printf("[Telegram] ⚠️ Ошибка создания SOCKS5 диалера: %v", err) } else { clients = append(clients, &http.Client{ Transport: &http.Transport{ DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { return dialer.Dial(network, addr) }, }, Timeout: timeout, }) } } // 3. Прямое соединение — всегда последний fallback clients = append(clients, &http.Client{Timeout: timeout}) return clients } // telegramDoPost выполняет POST запрос перебирая доступные клиенты по порядку. // Возвращает первый успешный ответ или последнюю ошибку если все варианты исчерпаны. func telegramDoPost(apiURL, contentType string, body []byte) (*http.Response, error) { clients := telegramHTTPClients() labels := telegramClientLabels() var lastErr error for i, client := range clients { resp, err := client.Post(apiURL, contentType, bytes.NewBuffer(body)) if err != nil { log.Printf("[Telegram] ⚠️ %s недоступен: %v", labels[i], err) lastErr = err continue } if i > 0 { log.Printf("[Telegram] ✓ Использован fallback: %s", labels[i]) } else { log.Printf("[Telegram] Отправка через: %s", labels[i]) } return resp, nil } return nil, lastErr } // telegramDoPostForm выполняет POST form запрос с перебором клиентов. func telegramDoPostForm(apiURL string, data url.Values) (*http.Response, error) { clients := telegramHTTPClients() labels := telegramClientLabels() var lastErr error for i, client := range clients { resp, err := client.PostForm(apiURL, data) if err != nil { log.Printf("[Telegram] ⚠️ %s недоступен: %v", labels[i], err) lastErr = err continue } if i > 0 { log.Printf("[Telegram] ✓ Использован fallback: %s", labels[i]) } else { log.Printf("[Telegram] Отправка через: %s", labels[i]) } return resp, nil } return nil, lastErr } // telegramClientLabels возвращает человекочитаемые названия клиентов в том же порядке. func telegramClientLabels() []string { var labels []string if cfgTelegramHTTPProxy != "" { labels = append(labels, fmt.Sprintf("HTTP прокси (%s)", cfgTelegramHTTPProxy)) } if cfgTelegramSocks5Proxy != "" { labels = append(labels, fmt.Sprintf("SOCKS5 (%s)", cfgTelegramSocks5Proxy)) } labels = append(labels, "прямое соединение") return labels } // telegramSendHTML отправляет HTML-сообщение в Telegram func telegramSendHTML(message string) error { if !isTelegramConfigured() { log.Printf("[Telegram] Не настроен. Сообщение:\n%s\n", message) return fmt.Errorf("telegram не настроен") } type tgMessage struct { ChatID string `json:"chat_id"` Text string `json:"text"` ParseMode string `json:"parse_mode"` } msg := tgMessage{ ChatID: cfgTelegramChatID, Text: message, ParseMode: "HTML", } jsonData, err := json.Marshal(msg) if err != nil { return fmt.Errorf("marshal error: %w", err) } apiURL := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", cfgTelegramBotToken) resp, err := telegramDoPost(apiURL, "application/json", jsonData) if err != nil { return fmt.Errorf("http error: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("telegram api error: status %d, body: %s", resp.StatusCode, string(body)) } return nil } // telegramSendPlainText отправляет текстовое сообщение без форматирования func telegramSendPlainText(message string) error { if !isTelegramConfigured() { log.Printf("[Telegram] Не настроен. Сообщение:\n%s\n", message) return fmt.Errorf("telegram не настроен") } apiURL := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", cfgTelegramBotToken) data := url.Values{} data.Set("chat_id", cfgTelegramChatID) data.Set("text", message) resp, err := telegramDoPostForm(apiURL, data) if err != nil { return fmt.Errorf("http error: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("telegram api error: status %d, body: %s", resp.StatusCode, string(body)) } return nil }