package main import ( "bytes" "encoding/json" "fmt" "io" "log" "net/http" "regexp" "strings" "time" ) // mattermostSend отправляет сообщение в Mattermost через Bot API. // HTML-теги из Telegram автоматически конвертируются в Markdown. // Если бот не настроен — пишет в лог и не паникует. func mattermostSend(message string) error { if !isMattermostConfigured() { log.Printf("[Mattermost] Не настроен (нужны MATTERMOST_URL, MATTERMOST_BOT_TOKEN, MATTERMOST_CHANNEL_ID) — пропускаем") return fmt.Errorf("mattermost не настроен") } mdMessage := mmHTMLToMarkdown(message) type mmPost struct { ChannelID string `json:"channel_id"` Message string `json:"message"` } payload := mmPost{ ChannelID: cfgMattermostChannelID, Message: mdMessage, } jsonData, err := json.Marshal(payload) if err != nil { return fmt.Errorf("marshal error: %w", err) } apiURL := fmt.Sprintf("%s/api/v4/posts", strings.TrimRight(cfgMattermostURL, "/")) log.Printf("[Mattermost] Отправка: %s (channel: %s)", apiURL, cfgMattermostChannelID) req, err := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonData)) if err != nil { return fmt.Errorf("ошибка создания запроса: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+cfgMattermostBotToken) client := &http.Client{Timeout: 15 * time.Second} resp, err := client.Do(req) if err != nil { return fmt.Errorf("ошибка отправки в Mattermost (%s): %w", apiURL, err) } defer resp.Body.Close() if resp.StatusCode != http.StatusCreated { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("mattermost api error: status %d, body: %s", resp.StatusCode, string(body)) } return nil } // mmHTMLToMarkdown конвертирует HTML-теги Telegram в Markdown для Mattermost. // // текст → **текст** // текст → _текст_ // текст → `текст` //
текст
→ ```текст``` // ━━━ (разделители) → --- // Остальные теги → удаляются func mmHTMLToMarkdown(html string) string { s := html // ... → **...** s = regexp.MustCompile(`([\s\S]*?)`).ReplaceAllString(s, "**$1**") // ... → _..._ s = regexp.MustCompile(`([\s\S]*?)`).ReplaceAllString(s, "_$1_") // ... → `...` s = regexp.MustCompile(`([\s\S]*?)`).ReplaceAllString(s, "`$1`") //
...
→ ```...``` s = regexp.MustCompile(`
([\s\S]*?)
`).ReplaceAllString(s, "```$1```") // HTML-entities s = strings.ReplaceAll(s, "<", "<") s = strings.ReplaceAll(s, ">", ">") s = strings.ReplaceAll(s, "&", "&") s = strings.ReplaceAll(s, """, `"`) // Горизонтальные разделители (━━━...) → --- s = regexp.MustCompile(`━+`).ReplaceAllString(s, "---") // Убираем оставшиеся HTML-теги s = regexp.MustCompile(`<[^>]+>`).ReplaceAllString(s, "") // Убираем лишние пустые строки (более двух подряд) s = regexp.MustCompile(`\n{3,}`).ReplaceAllString(s, "\n\n") return strings.TrimSpace(s) }