Files
OpenFlare/backend/plugins/domain/message_gateway/push/telegram.go
T
ryan ad8384182c autoresearch iter 22: delete lint suppressions that suppress nothing
24 of the 96 nolint directives were dead: they covered findings that no
longer exist. A stale suppression is not inert — it silently claims any
future finding for that linter in that scope, so a real problem raised
there would vanish without anyone noticing. Explanatory prose was kept as
ordinary comments.

Two directives proved load-bearing under the project gate even though
nolintlint reported them unused, and removing them exposed verified
contextcheck false positives: App.Run does forward a sigCtx derived from
the caller's context to Start, and the migration lock renewal must keep
its own deadline because the task context may already be canceled. Both
were restored, narrowed to the live linter, and given the reason the
originals lacked.
2026-08-29 09:54:33 +08:00

141 lines
3.7 KiB
Go

// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package push
import (
"Wavelet/pkg/httppool"
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
)
func init() {
Register("telegram", &TelegramPusher{})
}
// TelegramPusher Telegram 机器人推送实现
type TelegramPusher struct{}
type telegramMessageRequest struct {
ChatID string `json:"chat_id"`
Text string `json:"text"`
ParseMode string `json:"parse_mode,omitempty"`
}
type telegramErrorResponse struct {
Ok bool `json:"ok"`
ErrorCode int `json:"error_code"`
Description string `json:"description"`
}
// Send 执行 Telegram 消息发送
func (p *TelegramPusher) Send(ctx context.Context, cfg Config, target string, body map[string]any, template string, _ map[string]any) (string, error) {
if cfg.Secret == "" {
return "", errors.New("telegram: Bot Token (Secret) is required")
}
chatID := target
if chatID == "" {
chatID = cfg.Key // Use default chat ID (Key) if target is blank
}
if chatID == "" {
return "", errors.New("telegram: chat_id (target or default Key) is required")
}
baseURL := cfg.URL
if baseURL == "" {
baseURL = "https://api.telegram.org"
}
baseURL = strings.TrimSuffix(baseURL, "/")
title := bodyTitle(body)
content := bodyContent(body, "<b>%s</b>: %v", "\n")
level := bodyLevel(body)
var text string
if template != "" {
text = ParseTemplate(template, body)
} else {
text = fmt.Sprintf("<b>[%s] %s</b>\n\n%s", escapeHTML(level), escapeHTML(title), escapeHTML(content))
}
// Try sending with HTML parse mode
err := p.sendMessage(ctx, baseURL, cfg.Secret, chatID, text, "HTML")
if err != nil {
// Fallback: send as plain text without parse mode
plainText := text
if template == "" {
plainText = fmt.Sprintf("[%s] %s\n\n%s", level, title, content)
}
fallbackErr := p.sendMessage(ctx, baseURL, cfg.Secret, chatID, plainText, "")
if fallbackErr != nil {
return "", fmt.Errorf("telegram: send message failed (fallback also failed): %w (original HTML error: %w)", fallbackErr, err)
}
}
return "", nil
}
// ValidateConfig 校验 Telegram 配置
func (p *TelegramPusher) ValidateConfig(cfg Config) error {
if cfg.Secret == "" {
return errors.New("bot Token (Secret) is required")
}
if cfg.URL != "" {
if !strings.HasPrefix(cfg.URL, "http://") && !strings.HasPrefix(cfg.URL, "https://") {
return errors.New("API base URL must start with http:// or https://")
}
}
return nil
}
func (p *TelegramPusher) sendMessage(ctx context.Context, baseURL, token, chatID, text, parseMode string) error {
apiURL := fmt.Sprintf("%s/bot%s/sendMessage", baseURL, token)
reqPayload := telegramMessageRequest{
ChatID: chatID,
Text: text,
ParseMode: parseMode,
}
jsonData, err := json.Marshal(reqPayload)
if err != nil {
return fmt.Errorf("marshal request failed: %w", err)
}
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("create http request failed: %w", err)
}
httpReq.Header.Set("Content-Type", "application/json")
client := httppool.NewClient(defaultHTTPClientTimeout)
resp, err := client.Do(httpReq)
if err != nil {
return fmt.Errorf("http request failed: %w", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
var errRes telegramErrorResponse
if decodeErr := json.NewDecoder(resp.Body).Decode(&errRes); decodeErr == nil {
return fmt.Errorf("http status %d: %s", resp.StatusCode, errRes.Description)
}
return fmt.Errorf("http status %s", resp.Status)
}
return nil
}
func escapeHTML(s string) string {
s = strings.ReplaceAll(s, "&", "&amp;")
s = strings.ReplaceAll(s, "<", "&lt;")
s = strings.ReplaceAll(s, ">", "&gt;")
return s
}