Files
koin_ping_0.2.0/backend-go/internal/notifications/slack.go
2026-02-27 17:33:40 -05:00

129 lines
2.9 KiB
Go

package notifications
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"time"
)
type slackBlock struct {
Type string `json:"type"`
Text *slackText `json:"text,omitempty"`
Fields []slackText `json:"fields,omitempty"`
Elements []slackText `json:"elements,omitempty"`
}
type slackText struct {
Type string `json:"type"`
Text string `json:"text"`
}
type slackPayload struct {
Blocks []slackBlock `json:"blocks"`
}
func SendSlackNotification(webhookURL, message string, meta AlertMetadata) (bool, error) {
etherscanLink := ""
if meta.TxHash != "" {
etherscanLink = fmt.Sprintf("<%s|View on Etherscan>", "https://etherscan.io/tx/"+meta.TxHash)
}
blocks := []slackBlock{
{
Type: "header",
Text: &slackText{Type: "plain_text", Text: emojiForAlertType(meta.AlertType) + " Koin Ping Alert"},
},
{
Type: "section",
Text: &slackText{Type: "mrkdwn", Text: message},
},
{
Type: "section",
Fields: []slackText{
{Type: "mrkdwn", Text: fmt.Sprintf("*Address:*\n%s", meta.AddressLabel)},
{Type: "mrkdwn", Text: fmt.Sprintf("*Blockchain Address:*\n`%s`", meta.Address)},
},
},
}
if etherscanLink != "" {
blocks = append(blocks, slackBlock{
Type: "section",
Text: &slackText{Type: "mrkdwn", Text: fmt.Sprintf("*Transaction:* %s", etherscanLink)},
})
}
blocks = append(blocks, slackBlock{
Type: "context",
Elements: []slackText{
{Type: "mrkdwn", Text: fmt.Sprintf("Koin Ping | %s", time.Now().UTC().Format(time.RFC3339))},
},
})
payload := slackPayload{Blocks: blocks}
body, err := json.Marshal(payload)
if err != nil {
return false, fmt.Errorf("marshal slack payload: %w", err)
}
resp, err := http.Post(webhookURL, "application/json", bytes.NewReader(body))
if err != nil {
log.Printf("Failed to send Slack notification: %v", err)
return false, err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
log.Printf("Slack webhook failed: HTTP %d", resp.StatusCode)
return false, fmt.Errorf("slack webhook failed: HTTP %d", resp.StatusCode)
}
return true, nil
}
func TestSlackWebhook(webhookURL string) (bool, error) {
payload := slackPayload{
Blocks: []slackBlock{
{
Type: "section",
Text: &slackText{
Type: "mrkdwn",
Text: "Koin Ping test notification — Your Slack webhook is configured correctly!",
},
},
},
}
body, err := json.Marshal(payload)
if err != nil {
return false, err
}
resp, err := http.Post(webhookURL, "application/json", bytes.NewReader(body))
if err != nil {
log.Printf("Slack webhook test failed: %v", err)
return false, err
}
defer resp.Body.Close()
return resp.StatusCode >= 200 && resp.StatusCode < 300, nil
}
func emojiForAlertType(alertType string) string {
switch alertType {
case "incoming_tx":
return "📥"
case "outgoing_tx":
return "📤"
case "large_transfer":
return "🚨"
case "balance_below":
return "⚠️"
default:
return "🔔"
}
}