package notify import ( "bytes" "context" "encoding/json" "errors" "fmt" "net/http" ) // Telegram delivers notifications via the Telegram Bot API sendMessage // endpoint. Config is {"chat_id": "..."}; secret is the bot token and is // never logged. type Telegram struct { BaseURL string HTTP *http.Client } func (t *Telegram) Send(ctx context.Context, cfg json.RawMessage, secret string, ev Event) error { var c struct { ChatID string `json:"chat_id"` } if err := json.Unmarshal(cfg, &c); err != nil { return err } body, _ := json.Marshal(map[string]string{ "chat_id": c.ChatID, "text": fmt.Sprintf("[%s] %s → %s\n%s", ev.Project, ev.Domain, ev.Status, ev.Summary), }) url := t.BaseURL + "/bot" + secret + "/sendMessage" req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") resp, err := t.HTTP.Do(req) if err != nil { // Do NOT wrap/return err as-is: *url.Error.Error() embeds the full // request URL, which contains the bot token (/bot/...). A // caller logging this error would leak the secret. return errors.New("telegram: request failed") } defer resp.Body.Close() if resp.StatusCode >= 300 { return fmt.Errorf("telegram: status %d", resp.StatusCode) } return nil }