From a6d2f82633e9d8cc80f346ba673c317a4dbe788c Mon Sep 17 00:00:00 2001 From: Vladimir V Maksimov Date: Sun, 14 Jun 2026 11:05:14 +0300 Subject: [PATCH] =?UTF-8?q?docs:=20Pachca=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7?= =?UTF-8?q?=20=D0=B2=D1=85=D0=BE=D0=B4=D1=8F=D1=89=D0=B8=D0=B9=20=D0=B2?= =?UTF-8?q?=D0=B5=D0=B1=D1=85=D1=83=D0=BA=20=D0=B2=D0=BC=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D0=BE=20API-=D1=82=D0=BE=D0=BA=D0=B5=D0=BD=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../plans/2026-06-14-notification-channels.md | 45 +++++++++++++------ ...2026-06-14-notification-channels-design.md | 15 ++++--- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/docs/superpowers/plans/2026-06-14-notification-channels.md b/docs/superpowers/plans/2026-06-14-notification-channels.md index ba52f61..ad2f385 100644 --- a/docs/superpowers/plans/2026-06-14-notification-channels.md +++ b/docs/superpowers/plans/2026-06-14-notification-channels.md @@ -99,8 +99,7 @@ type ConfigTelegram struct { } type ConfigPachca struct { - Token string `yaml:"token"` - ChatID int64 `yaml:"chat_id"` + WebhookURL string `yaml:"webhook_url"` // входящий вебхук Pachca } type ConfigEmail struct { @@ -394,11 +393,17 @@ Expected: FAIL / build error — `formatMarkdown` и `formatHTML` не суще package main import ( + "bytes" + "encoding/json" "fmt" "html" + "io" + "net" + "net/http" "net/mail" "net/smtp" "strings" + "time" "gitea.mediatoday.ru/mt/notify" ) @@ -446,16 +451,28 @@ func (t *telegramNotifier) Send(d RequestData) error { return err } -// --- Pachca --- +// --- Pachca (входящий вебхук) --- type pachcaNotifier struct { - client *notify.Pachca - chatID int64 + webhookURL string + client *http.Client } func (p *pachcaNotifier) Send(d RequestData) error { - _, err := p.client.SendMessage(p.chatID, formatMarkdown(d)) - return err + payload, err := json.Marshal(map[string]string{"message": formatMarkdown(d)}) + if err != nil { + return err + } + resp, err := p.client.Post(p.webhookURL, "application/json", bytes.NewReader(payload)) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + body, _ := io.ReadAll(resp.Body) + return fmt.Errorf("pachca webhook: HTTP %d: %s", resp.StatusCode, string(body)) + } + return nil } // --- Email --- @@ -488,9 +505,12 @@ func BuildNotifiers(cfg *Config) (map[string]Notifier, error) { } if cfg.Pachca != nil { + if cfg.Pachca.WebhookURL == "" { + return nil, fmt.Errorf("pachca: не задан webhook_url") + } notifiers["pachca"] = &pachcaNotifier{ - client: notify.NewPachca(cfg.Pachca.Token), - chatID: cfg.Pachca.ChatID, + webhookURL: cfg.Pachca.WebhookURL, + client: &http.Client{Timeout: 30 * time.Second}, } } @@ -536,14 +556,12 @@ func BuildNotifiers(cfg *Config) (map[string]Notifier, error) { Добавить хелпер извлечения хоста (в notifier.go, под `BuildNotifiers`): ```go -import "net" // добавить в блок импортов notifier.go - func splitHostPortLenient(addr string) (host, port string, err error) { return net.SplitHostPort(addr) } ``` -> Примечание: `splitHostPortLenient` — тонкая обёртка над `net.SplitHostPort`; можно вызвать `net.SplitHostPort` напрямую и убрать хелпер. Оставлено для явности. Не забудь добавить `"net"` в импорты notifier.go. +> Примечание: `splitHostPortLenient` — тонкая обёртка над `net.SplitHostPort`; можно вызвать `net.SplitHostPort` напрямую и убрать хелпер. Оставлено для явности. `"net"` уже включён в блок импортов выше. - [ ] **Step 4: Запустить тесты — убедиться, что проходят** @@ -757,8 +775,7 @@ telegram: disable_ipv6: true # опционально, по умолчанию true pachca: - token: "*****" - chat_id: 12345 + webhook_url: "https://api.pachca.com/webhooks/XXXX" # входящий вебхук email: smtp_addr: "smtp.example.com:587" diff --git a/docs/superpowers/specs/2026-06-14-notification-channels-design.md b/docs/superpowers/specs/2026-06-14-notification-channels-design.md index f289a5d..4296d64 100644 --- a/docs/superpowers/specs/2026-06-14-notification-channels-design.md +++ b/docs/superpowers/specs/2026-06-14-notification-channels-design.md @@ -61,8 +61,13 @@ type Notifier interface { - **`telegramNotifier{bot *notify.Bot, chatID int64}`** Форматирует тело в ` ``` `-блок (как сейчас), отправляет `bot.SendTextMessage` (ParseMode Markdown). -- **`pachcaNotifier{client *notify.Pachca, chatID int64}`** - Форматирует в markdown ` ``` `-блок, отправляет `client.SendMessage(chatID, text)`. +- **`pachcaNotifier{webhookURL string, client *http.Client}`** + Использует **входящий вебхук** Pachca (incoming webhook), а не API-токен. + Форматирует в markdown ` ``` `-блок и POST-ит `{"message": text}` (JSON, + `Content-Type: application/json`) на `webhook_url`. Токен/chat_id не нужны — + идентификатор в URL и есть авторизация; сообщение уходит во все групповые чаты, + где состоит бот. Библиотечный `notify.Pachca` (Bearer API + chat_id) для этого + НЕ используется. Проверено: тестовый POST вернул `200`. - **`emailNotifier{auth notify.SmtpAuth, from mail.Address, to []mail.Address, subject string}`** Форматирует тело в `
` с HTML-экранированием содержимого, отправляет `notify.SendEmailHTML(auth, body, subject, from, to...)`. @@ -88,8 +93,7 @@ telegram: disable_ipv6: true # опционально, по умолчанию true pachca: - token: "*****" - chat_id: 12345 + webhook_url: "https://api.pachca.com/webhooks/XXXX" # входящий вебхук email: smtp_addr: "smtp.example.com:587" @@ -119,8 +123,7 @@ type ConfigTelegram struct { } type ConfigPachca struct { - Token string `yaml:"token"` - ChatID int64 `yaml:"chat_id"` + WebhookURL string `yaml:"webhook_url"` // входящий вебхук Pachca } type ConfigEmail struct {