imap-notify/main.go
2025-06-23 00:48:54 +02:00

202 lines
4.8 KiB
Go

package main
import (
"crypto/tls"
"fmt"
"io"
"log"
"net/http"
"os"
"strings"
"time"
"github.com/emersion/go-imap"
"github.com/emersion/go-imap/client"
"github.com/emersion/go-message/mail"
)
func main() {
log.SetOutput(os.Stdout)
log.Println("IMAP-ntfy Notification Service gestartet")
imapServer := os.Getenv("IMAP_SERVER")
imapPort := os.Getenv("IMAP_PORT")
emailUser := os.Getenv("EMAIL_USER")
emailPass := os.Getenv("EMAIL_PASSWORD")
ntfyServer := os.Getenv("NTFY_SERVER")
ntfyTopic := os.Getenv("NTFY_TOPIC")
ntfyUser := os.Getenv("NTFY_USER")
ntfyPass := os.Getenv("NTFY_PASSWORD")
if ntfyServer == "" {
ntfyServer = "https://ntfy.sh"
log.Println("NTFY_SERVER nicht gesetzt, verwende Standardwert:", ntfyServer)
}
for {
log.Println("Starte IMAP-Abfrage...")
addr := fmt.Sprintf("%s:%s", imapServer, imapPort)
c, err := client.DialTLS(addr, &tls.Config{InsecureSkipVerify: true})
if err != nil {
log.Printf("IMAP-Verbindungsfehler: %v", err)
time.Sleep(1 * time.Minute)
continue
}
defer c.Logout()
if err := c.Login(emailUser, emailPass); err != nil {
log.Printf("IMAP-Login fehlgeschlagen: %v", err)
time.Sleep(1 * time.Minute)
continue
}
_, err = c.Select("INBOX", false)
if err != nil {
log.Printf("Postfachauswahl fehlgeschlagen: %v", err)
time.Sleep(1 * time.Minute)
continue
}
criteria := imap.NewSearchCriteria()
criteria.WithoutFlags = []string{"\\Seen"}
ids, err := c.Search(criteria)
if err != nil {
log.Printf("Suche fehlgeschlagen: %v", err)
time.Sleep(1 * time.Minute)
continue
}
if len(ids) > 0 {
log.Printf("Gefunden: %d neue Nachrichten", len(ids))
seqset := new(imap.SeqSet)
seqset.AddNum(ids...)
section := &imap.BodySectionName{}
items := []imap.FetchItem{imap.FetchEnvelope, section.FetchItem()}
messages := make(chan *imap.Message, 10)
go func() {
if err := c.Fetch(seqset, items, messages); err != nil {
log.Printf("Fetch fehlgeschlagen: %v", err)
}
}()
for msg := range messages {
subject := msg.Envelope.Subject
from := ""
if len(msg.Envelope.From) > 0 {
from = msg.Envelope.From[0].Address()
}
replyTo := ""
if len(msg.Envelope.ReplyTo) > 0 {
replyTo = msg.Envelope.ReplyTo[0].Address()
} else if from != "" {
replyTo = from
}
var bodyText string
if r := msg.GetBody(section); r != nil {
bodyBytes, err := io.ReadAll(r)
if err != nil {
log.Printf("Fehler beim Lesen des Bodys: %v", err)
} else {
bodyText = extractTextFromBody(bodyBytes)
if len(bodyText) > 200 {
bodyText = bodyText[:200] + "..."
}
}
}
message := fmt.Sprintf(
"Betreff: %s\nVon: %s\nAntwort an: %s\n\n%s",
subject, from, replyTo, bodyText,
)
sendNtfyNotification(ntfyServer, ntfyTopic, ntfyUser, ntfyPass,
subject, message, replyTo)
}
item := imap.FormatFlagsOp(imap.AddFlags, true)
flags := []interface{}{imap.SeenFlag}
if err := c.Store(seqset, item, flags, nil); err != nil {
log.Printf("Markieren fehlgeschlagen: %v", err)
}
} else {
log.Println("Keine neuen Nachrichten gefunden.")
}
c.Logout()
log.Println("Warte 1 Minute bis zur nächsten Abfrage...")
time.Sleep(1 * time.Minute)
}
}
func extractTextFromBody(bodyBytes []byte) string {
r := strings.NewReader(string(bodyBytes))
mr, err := mail.CreateReader(r)
if err != nil {
return string(bodyBytes)
}
var textContent strings.Builder
for {
p, err := mr.NextPart()
if err == io.EOF {
break
}
if err != nil {
continue
}
switch h := p.Header.(type) {
case *mail.InlineHeader:
contentType, _, _ := h.ContentType()
if contentType == "text/plain" {
partBytes, _ := io.ReadAll(p.Body)
textContent.Write(partBytes)
}
}
}
if textContent.Len() > 0 {
return textContent.String()
}
return string(bodyBytes)
}
func sendNtfyNotification(server, topic, user, pass, title, message, replyTo string) {
url := fmt.Sprintf("%s/%s", server, topic)
log.Printf("Sende ntfy-Benachrichtigung an %s", url)
req, _ := http.NewRequest("POST", url, strings.NewReader(message))
req.Header.Set("Title", title)
req.Header.Set("Content-Type", "text/plain")
if replyTo != "" {
action := fmt.Sprintf("view, Antworten, mailto:%s", replyTo)
req.Header.Set("Actions", action)
log.Printf("Füge Action-Button hinzu: %s", action)
}
if user != "" && pass != "" {
req.SetBasicAuth(user, pass)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Printf("ntfy-Fehler: %v", err)
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
log.Printf("ntfy-Fehlerstatus: %d", resp.StatusCode)
log.Printf("Fehlermeldung: %s", string(body))
} else {
log.Println("ntfy-Benachrichtigung erfolgreich gesendet")
}
}