Files
ncore-hnr/internal/notify/notify.go
Zsolt Alföldi 469e5b0678 init
2026-05-07 00:14:02 +02:00

132 lines
3.3 KiB
Go

package notify
import (
"bytes"
"fmt"
"net"
"net/http"
"net/mail"
"net/smtp"
"strings"
"time"
"ncore-hnr/internal/model"
)
const manualNeededSubject = "nCore HnR manual work"
type Sender interface {
SendManualNeeded(results []model.ActionResult) error
}
type NotificationNTFY struct {
URL string
HTTPClient *http.Client
}
func (n NotificationNTFY) SendManualNeeded(results []model.ActionResult) error {
body, ok := manualNeededText(results)
if strings.TrimSpace(n.URL) == "" || !ok {
return nil
}
client := n.HTTPClient
if client == nil {
client = &http.Client{Timeout: 15 * time.Second}
}
req, err := http.NewRequest(http.MethodPost, n.URL, bytes.NewBufferString(body))
if err != nil {
return err
}
req.Header.Set("Content-Type", "text/plain; charset=utf-8")
req.Header.Set("Title", manualNeededSubject)
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("notify returned %s", resp.Status)
}
return nil
}
type NotificationSMTP struct {
Host string
Port string
Username string
Password string
From string
To string
}
func (s NotificationSMTP) SendManualNeeded(results []model.ActionResult) error {
body, ok := manualNeededText(results)
if strings.TrimSpace(s.Host) == "" || !ok {
return nil
}
host := strings.TrimSpace(s.Host)
addr := net.JoinHostPort(host, strings.TrimSpace(s.Port))
from, err := mail.ParseAddress(strings.TrimSpace(s.From))
if err != nil {
return fmt.Errorf("parse smtp from: %w", err)
}
recipients, err := mail.ParseAddressList(strings.TrimSpace(s.To))
if err != nil {
return fmt.Errorf("parse smtp to: %w", err)
}
to := make([]string, 0, len(recipients))
for _, recipient := range recipients {
to = append(to, recipient.Address)
}
var auth smtp.Auth
if strings.TrimSpace(s.Username) != "" || strings.TrimSpace(s.Password) != "" {
auth = smtp.PlainAuth("", strings.TrimSpace(s.Username), strings.TrimSpace(s.Password), host)
}
message := strings.Builder{}
message.WriteString(fmt.Sprintf("From: %s\r\n", from.String()))
message.WriteString(fmt.Sprintf("To: %s\r\n", formatAddressList(recipients)))
message.WriteString(fmt.Sprintf("Subject: %s\r\n", manualNeededSubject))
message.WriteString("MIME-Version: 1.0\r\n")
message.WriteString("Content-Type: text/plain; charset=UTF-8\r\n")
message.WriteString("\r\n")
message.WriteString(body)
if err := smtp.SendMail(addr, auth, from.Address, to, []byte(message.String())); err != nil {
return fmt.Errorf("send smtp notification: %w", err)
}
return nil
}
func manualNeededText(results []model.ActionResult) (string, bool) {
var body strings.Builder
manualCount := 0
body.WriteString("nCore HnR torrents need manual work:\n")
for _, result := range results {
if !result.ManualNeeded {
continue
}
manualCount++
body.WriteString(fmt.Sprintf("- %s", result.Torrent.Name))
if result.QBit != nil {
body.WriteString(fmt.Sprintf(" (qBit: %s, %.1f%%)", result.QBit.State, result.QBit.Progress*100))
}
body.WriteString("\n")
}
return body.String(), manualCount > 0
}
func formatAddressList(addresses []*mail.Address) string {
formatted := make([]string, 0, len(addresses))
for _, address := range addresses {
formatted = append(formatted, address.String())
}
return strings.Join(formatted, ", ")
}