package main import ( "bytes" "context" "encoding/json" "fmt" "log" "net/http" "os" "time" ) // Konfiguration aus Umgebungsvariablen var ( gotosocialURL = os.Getenv("GOTOSOCIAL_URL") accessToken = os.Getenv("GOTOSOCIAL_TOKEN") ntfyServer = os.Getenv("NTFY_SERVER") ntfyToken = os.Getenv("NTFY_TOKEN") ntfyTopic = os.Getenv("NTFY_TOPIC") pollInterval, _ = time.ParseDuration(os.Getenv("POLL_INTERVAL")) ) type Notification struct { ID string `json:"id"` Type string `json:"type"` CreatedAt string `json:"created_at"` Account struct { DisplayName string `json:"display_name"` } `json:"account"` Status struct { Content string `json:"content"` } `json:"status"` } type NtfyMessage struct { Topic string `json:"topic"` Title string `json:"title"` Message string `json:"message"` Tags []string `json:"tags"` Priority int `json:"priority"` } func main() { log.SetFlags(log.LstdFlags | log.Lmicroseconds | log.Lshortfile) log.Println("Starte GoToSocial-Notifier...") if pollInterval == 0 { pollInterval = 30 * time.Second log.Printf("Kein POLL_INTERVAL gesetzt, verwende Default: %s", pollInterval) } if gotosocialURL == "" || accessToken == "" || ntfyServer == "" || ntfyTopic == "" { log.Fatal("Eine oder mehrere erforderliche Umgebungsvariablen fehlen (GOTOSOCIAL_URL, GOTOSOCIAL_TOKEN, NTFY_SERVER, NTFY_TOPIC)") } ticker := time.NewTicker(pollInterval) defer ticker.Stop() lastID := "" for { select { case <-ticker.C: log.Println("Frage GoToSocial-Benachrichtigungen ab ...") notifications, err := fetchNotifications(lastID) if err != nil { log.Printf("Fehler beim Abrufen: %v", err) continue } if len(notifications) > 0 { lastID = notifications[0].ID log.Printf("%d neue Benachrichtigungen gefunden.", len(notifications)) } else { log.Println("Keine neuen Benachrichtigungen.") } for _, n := range notifications { if err := sendToNtfy(n); err != nil { log.Printf("Fehler beim Senden an ntfy: %v", err) } else { log.Printf("Benachrichtigung '%s' an ntfy gesendet.", n.ID) } } } } } func fetchNotifications(sinceID string) ([]Notification, error) { req, err := http.NewRequestWithContext( context.Background(), "GET", gotosocialURL+"/api/v1/notifications", nil, ) if err != nil { return nil, err } req.Header.Set("Authorization", "Bearer "+accessToken) q := req.URL.Query() q.Add("limit", "10") if sinceID != "" { q.Add("since_id", sinceID) } req.URL.RawQuery = q.Encode() resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("Statuscode: %d", resp.StatusCode) } var notifications []Notification if err := json.NewDecoder(resp.Body).Decode(¬ifications); err != nil { return nil, err } return notifications, nil } func sendToNtfy(n Notification) error { msg := NtfyMessage{ Topic: ntfyTopic, Title: fmt.Sprintf("Neue Benachrichtigung von %s", n.Account.DisplayName), Message: fmt.Sprintf("Typ: %s\n\n%s", n.Type, n.Status.Content), Tags: []string{"bell", "incoming_envelope"}, Priority: 4, } jsonData, err := json.Marshal(msg) if err != nil { return err } req, err := http.NewRequest( "POST", ntfyServer, bytes.NewBuffer(jsonData), ) if err != nil { return err } req.Header.Set("Content-Type", "application/json") if ntfyToken != "" { req.Header.Set("Authorization", "Bearer "+ntfyToken) } resp, err := http.DefaultClient.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("ntfy antwortete mit: %d", resp.StatusCode) } return nil }