commit ca377f660b00f223508dc8bea58dff52fe0ec726 Author: Simon Rieger Date: Tue Jul 1 20:28:36 2025 +0200 first commit diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..5c71596 --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +# GoToSocial Konfiguration +GOTOSOCIAL_URL=https://your.gotosocial.instance +GOTOSOCIAL_TOKEN=your_gotosocial_token + +# ntfy Konfiguration +NTFY_SERVER=https://your.ntfy.server +NTFY_TOKEN=your_ntfy_token # Optional für authentifizierte Server +NTFY_TOPIC=your_topic_name # Benutzerdefiniertes Topic + +# Abfrageintervall (z.B. 30s, 5m) +POLL_INTERVAL=30s diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c49bd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..85756f5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM golang:1.24-alpine AS builder +RUN apk add --no-cache git +WORKDIR /app +COPY go.mod ./ +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 GOOS=linux go build -o gots-notify . + +FROM alpine:latest +RUN apk --no-cache add ca-certificates +WORKDIR /app +COPY --from=builder /app/gots-notify . +CMD ["./gots-notify"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..8bb124d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,9 @@ +version: '3.8' + +services: + gots-notify: + build: . + container_name: gots-notify + restart: unless-stopped + env_file: + - .env diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..51b5488 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module gots-notify + +go 1.24.1 diff --git a/main.go b/main.go new file mode 100644 index 0000000..219155e --- /dev/null +++ b/main.go @@ -0,0 +1,152 @@ +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() { + if pollInterval == 0 { + pollInterval = 30 * time.Second + } + + ticker := time.NewTicker(pollInterval) + defer ticker.Stop() + + lastID := "" + + for { + select { + case <-ticker.C: + notifications, err := fetchNotifications(lastID) + if err != nil { + log.Printf("Fehler beim Abrufen: %v", err) + continue + } + + if len(notifications) > 0 { + lastID = notifications[0].ID + } + + for _, n := range notifications { + if err := sendToNtfy(n); err != nil { + log.Printf("Fehler beim Senden an ntfy: %v", err) + } + } + } + } +} + +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 +}