diff --git a/config.example.yaml b/config.example.yaml index 9fe438e..eb37f59 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -2,12 +2,18 @@ ip_provider: type: plain config: - url: https://ifconfig.me + url: https://ifconfig.me dns_provider: type: ionos config: api_key: base_url: https://api.hosting.ionos.com/dns +notification_provider: + type: gotify + config: + url: + token: + priority: 0 domains: - tld: example.com subdomains: diff --git a/main.go b/main.go index 2f1837e..9202ca6 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,8 @@ package main import ( "fmt" + "time" + "realdnydns/pkg/changeDetector" "realdnydns/pkg/config" "realdnydns/pkg/dnsProvider" @@ -10,7 +12,7 @@ import ( plainExternalIpProvider "realdnydns/pkg/externalIpProvider/plain" "realdnydns/pkg/notificationProvider" notificationProviderConsole "realdnydns/pkg/notificationProvider/console" - "time" + gotify "realdnydns/pkg/notificationProvider/gotify" "github.com/go-co-op/gocron" ) @@ -54,6 +56,17 @@ func main() { var notificationProvider notificationProvider.NotificationProvider switch configClient.NotificationProvider.Type { + case "gotify": + var gotifyConfig gotify.NotificationProviderImplGotifyConfig + err := configClient.NotificationProvider.ProviderConfig.Decode(&gotifyConfig) + if err != nil { + panic(err) + } + + notificationProvider, err = gotify.New(gotifyConfig) + if err != nil { + panic(err) + } default: // Use default console notification provider notificationProvider = notificationProviderConsole.New() diff --git a/pkg/notificationProvider/gotify/gotify.go b/pkg/notificationProvider/gotify/gotify.go new file mode 100644 index 0000000..4966c05 --- /dev/null +++ b/pkg/notificationProvider/gotify/gotify.go @@ -0,0 +1,87 @@ +package notificationProviderGotify + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/url" +) + +type NotificationProviderImplGotifyConfig struct { + Url string `yaml:"url"` + Token string `yaml:"token"` + Priority int `yaml:"priority"` +} + +type NotificationProviderImplGotify struct { + Url url.URL + Token string + Priority int + HTTPClient *http.Client +} + +func New(config NotificationProviderImplGotifyConfig) (*NotificationProviderImplGotify, error) { + if config.Url == "" { + return nil, fmt.Errorf("url is required") + } + + url, err := url.Parse(config.Url) + if err != nil { + return nil, err + } + + if config.Token == "" { + return nil, fmt.Errorf("token is required") + } + + if config.Priority < 0 || config.Priority > 4 { + return nil, fmt.Errorf("priority must be between 0 and 4") + } + + return &NotificationProviderImplGotify{ + *url, + config.Token, + config.Priority, + &http.Client{}, + }, nil +} + +func (p *NotificationProviderImplGotify) SendNotification(title string, message string) error { + type GotifyMessage struct { + Message string `json:"message"` + Title string `json:"title"` + Priority int `json:"priority"` + } + + messageJson, err := json.Marshal(GotifyMessage{ + message, + title, + p.Priority, + }) + if err != nil { + return err + } + + messageUrl := p.Url + messageUrl.JoinPath("message") + messageUrl.Query().Add("token", p.Token) + + req, err := http.NewRequest("POST", messageUrl.String(), bytes.NewBuffer(messageJson)) + if err != nil { + return err + } + + req.Header.Set("Content-Type", "application/json") + + res, err := p.HTTPClient.Do(req) + if err != nil { + return err + } + + if res.StatusCode != 200 { + return fmt.Errorf("unexpected status code: %d", res.StatusCode) + } + + return nil +} diff --git a/pkg/notificationProvider/gotify/gotify_test.go b/pkg/notificationProvider/gotify/gotify_test.go new file mode 100644 index 0000000..70b3661 --- /dev/null +++ b/pkg/notificationProvider/gotify/gotify_test.go @@ -0,0 +1,81 @@ +package notificationProviderGotify_test + +import ( + "net/http" + "net/http/httptest" + "testing" + + gotify "realdnydns/pkg/notificationProvider/gotify" +) + +func TestNew(t *testing.T) { + t.Run("URL is required", testNewEmptyUrl()) + t.Run("Token is required", testNewEmptyToken()) + t.Run("Priority must be between 0 and 4", testNewInvalidPriority()) + t.Run("Sends POST request to url", testNewSendsPostRequest()) +} + +func testNewEmptyUrl() func(t *testing.T) { + return func(t *testing.T) { + _, err := gotify.New(gotify.NotificationProviderImplGotifyConfig{ + Token: "1234", + Priority: 1, + }) + + if err.Error() != "url is required" { + t.Errorf("Expected error 'url is required', got %v", err) + } + } +} + +func testNewEmptyToken() func(t *testing.T) { + return func(t *testing.T) { + _, err := gotify.New(gotify.NotificationProviderImplGotifyConfig{ + Url: "http://localhost:1234", + Priority: 0, + }) + + if err.Error() != "token is required" { + t.Errorf("Expected error 'token is required', got %v", err) + } + } +} + +func testNewInvalidPriority() func(t *testing.T) { + return func(t *testing.T) { + _, err := gotify.New(gotify.NotificationProviderImplGotifyConfig{ + Url: "http://localhost:1234", + Token: "token", + Priority: 5, + }) + + if err.Error() != "priority must be between 0 and 4" { + t.Errorf("Expected error 'priority must be between 0 and 4', got %v", err) + } + } +} + +func testNewSendsPostRequest() func(t *testing.T) { + return func(t *testing.T) { + mockServer := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + }, + )) + defer mockServer.Close() + + provider, err := gotify.New(gotify.NotificationProviderImplGotifyConfig{ + Url: mockServer.URL, + Token: "1234", + Priority: 0, + }) + if err != nil { + t.Fatalf("New() returned unexpected error: %v", err) + } + + err = provider.SendNotification("title", "message") + if err != nil { + t.Fatalf("SendNotification() returned unexpected error: %v", err) + } + } +}