From 885ff3b9281c7df97ef5f98d5ebe9118b3274d8a Mon Sep 17 00:00:00 2001 From: Cato Date: Tue, 1 Jul 2025 22:40:05 +0200 Subject: [PATCH] initial commit --- .gitignore | 1 + awl.go | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++ default.nix | 21 ++++++++++++ flake.lock | 85 +++++++++++++++++++++++++++++++++++++++++++++++ flake.nix | 31 ++++++++++++++++++ go.mod | 8 +++++ go.sum | 7 ++++ gomod2nix.toml | 3 ++ main.go | 52 +++++++++++++++++++++++++++++ ntfy.go | 46 ++++++++++++++++++++++++++ shell.nix | 24 ++++++++++++++ 11 files changed, 367 insertions(+) create mode 100644 .gitignore create mode 100644 awl.go create mode 100644 default.nix create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 go.mod create mode 100644 go.sum create mode 100644 gomod2nix.toml create mode 100644 main.go create mode 100644 ntfy.go create mode 100644 shell.nix diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dfba653 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/gomod2nix-template diff --git a/awl.go b/awl.go new file mode 100644 index 0000000..1f60f49 --- /dev/null +++ b/awl.go @@ -0,0 +1,89 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" +) + +const AWL_URL string = "https://buergerportal.awl-neuss.de/api/v1/calendar" + +func GetStreetNumbers() (map[string]int, error) { + url := AWL_URL + "/townarea-streets" + response, err := http.Get(url) + if err != nil { + return nil, err + } + defer response.Body.Close() + + content, err := io.ReadAll(response.Body) + if err != nil { + return nil, err + } + + var streets []struct { + Number int `json:"strasseNummer"` + Name string `json:"strasseBezeichnung"` + } + err = json.Unmarshal(content, &streets) + if err != nil { + return nil, err + } + + nameToNumber := make(map[string]int, len(streets)) + for _, street := range streets { + name := strings.ToLower(street.Name) + name = strings.Replace(name, " ", "-", -1) + name = strings.Replace(name, "--", "-", -1) + nameToNumber[name] = street.Number + } + + return nameToNumber, nil +} + +func AwlTomorrow(street, home int) ([]string, error) { + startDate := time.Now().AddDate(0, 0, 1) + startDateFmt := startDate.Format("Jan 02 2006") + + requestUrl := fmt.Sprintf(AWL_URL + "?startMonth=%s&streetNum=%d&homeNumber=%d", url.QueryEscape(startDateFmt), street, home) + response, err := http.Get(requestUrl) + if err != nil { + return nil, err + } + defer response.Body.Close() + + content, err := io.ReadAll(response.Body) + if err != nil { + return nil, err + } + + var jsonBody map[string]map[string][]string + err = json.Unmarshal(content, &jsonBody) + if err != nil { + return nil, fmt.Errorf("could not parse json:\n%s\n%s", err.Error(), content) + } + + if len(jsonBody) != 1 { + return nil, fmt.Errorf("unexpected awl response:\n%s", string(content)) + } + + currentMonth := fmt.Sprintf("%d-%d", startDate.Month()-1, startDate.Year()) + currentDay := startDate.Format("2") + + month, ok := jsonBody[currentMonth] + if !ok { + return nil, fmt.Errorf("could not access month (%s):\n%s", currentMonth, string(content)) + } + + day, ok := month[currentDay] + if !ok { + return nil, fmt.Errorf("could not access day (%s):\n%s", currentDay, string(content)) + } + + return day, nil +} + diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..3f96341 --- /dev/null +++ b/default.nix @@ -0,0 +1,21 @@ +{ pkgs ? ( + let + inherit (builtins) fetchTree fromJSON readFile; + inherit ((fromJSON (readFile ./flake.lock)).nodes) nixpkgs gomod2nix; + in + import (fetchTree nixpkgs.locked) { + overlays = [ + (import "${fetchTree gomod2nix.locked}/overlay.nix") + ]; + } + ) +, buildGoApplication ? pkgs.buildGoApplication +}: + + buildGoApplication { + pname = "awl-ntfy"; + version = "0.1"; + pwd = ./.; + src = ./.; + modules = ./gomod2nix.toml; + } diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..a151ef3 --- /dev/null +++ b/flake.lock @@ -0,0 +1,85 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gomod2nix": { + "inputs": { + "flake-utils": [ + "flake-utils" + ], + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1750314194, + "narHash": "sha256-SjpXWEeB+UIMzuCAF94PuyAXpJdnBLF45JvI6o4wKIU=", + "owner": "nix-community", + "repo": "gomod2nix", + "rev": "a5f75f563748599d448a4a076816041d7b0fc07e", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "gomod2nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1751271578, + "narHash": "sha256-P/SQmKDu06x8yv7i0s8bvnnuJYkxVGBWLWHaU+tt4YY=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "3016b4b15d13f3089db8a41ef937b13a9e33a8df", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "gomod2nix": "gomod2nix", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..d09d65e --- /dev/null +++ b/flake.nix @@ -0,0 +1,31 @@ +{ + description = "A basic gomod2nix flake"; + + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + inputs.flake-utils.url = "github:numtide/flake-utils"; + + inputs.gomod2nix = { + url = "github:nix-community/gomod2nix"; + inputs = { + nixpkgs.follows = "nixpkgs"; + flake-utils.follows = "flake-utils"; + }; + }; + + outputs = { self, nixpkgs, flake-utils, gomod2nix }: + (flake-utils.lib.eachDefaultSystem + (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + callPackage = pkgs.callPackage; + in + { + packages.default = callPackage ./. { + inherit (gomod2nix.legacyPackages.${system}) buildGoApplication; + }; + devShells.default = callPackage ./shell.nix { + inherit (gomod2nix.legacyPackages.${system}) mkGoEnv gomod2nix; + }; + }) + ); +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..49b5f19 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module catos.directory/tasks/awl-ntfy + +go 1.24.2 + +require ( + github.com/alexflint/go-arg v1.5.1 // indirect + github.com/alexflint/go-scalar v1.2.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..40ad00b --- /dev/null +++ b/go.sum @@ -0,0 +1,7 @@ +github.com/alexflint/go-arg v1.5.1 h1:nBuWUCpuRy0snAG+uIJ6N0UvYxpxA0/ghA/AaHxlT8Y= +github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8= +github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw= +github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= diff --git a/gomod2nix.toml b/gomod2nix.toml new file mode 100644 index 0000000..d0d5a65 --- /dev/null +++ b/gomod2nix.toml @@ -0,0 +1,3 @@ +schema = 1 + +[mod] diff --git a/main.go b/main.go new file mode 100644 index 0000000..4ff7299 --- /dev/null +++ b/main.go @@ -0,0 +1,52 @@ +package main + +import ( + "fmt" + "os" + + arg "github.com/alexflint/go-arg" +) + +func main() { + var args struct { + Street string `arg:"positional,required"` + Home int `arg:"positional,required"` + } + + parser, err := arg.NewParser(arg.Config{}, &args) + if err != nil { + fmt.Println(err) + return + } + parser.MustParse(os.Args[1:]) + + streetNumbers, err := GetStreetNumbers() + if err != nil { + fmt.Println(err) + _ = SendErr(args.Street, err) + return + } + + streetNumber, ok := streetNumbers[args.Street] + if !ok { + fmt.Println("street could not be found:", args.Street) + } + + tomorrow, err := AwlTomorrow(streetNumber, args.Home) + if err != nil { + fmt.Println(err) + _ = SendErr(args.Street, err) + return + } + + if len(tomorrow) == 0 { + return + } + + err = SendAwlNotification(args.Street, tomorrow) + if err != nil { + fmt.Println(err) + _ = SendErr(args.Street, err) + return + } +} diff --git a/ntfy.go b/ntfy.go new file mode 100644 index 0000000..b62cf6e --- /dev/null +++ b/ntfy.go @@ -0,0 +1,46 @@ +package main + +import ( + "fmt" + "net/http" + "strings" +) + +func SendAwlNotification(channel string, binColors []string) error { + var bins = make([]string, 0, 5) + for _, color := range binColors { + switch color { + case "pink": + bins = append(bins, "🟣 Pink") + case "gelb": + bins = append(bins, "🟡 Gelb") + case "blau": + bins = append(bins, "🔵 Blau") + case "grau": + bins = append(bins, "⚫ Grau") + case "braun": + bins = append(bins, "🟤 Braun") + } + } + + var message string + if len(bins) == 1 { + message = "Morgen wird folgende Tonne abgeholt: " + } else { + message = "Morgen werden folgende Tonnen abgeholt: " + } + message += strings.Join(bins, ", ") + return SendNotification(channel, message) +} + +func SendNotification(channel, message string) error { + topic := fmt.Sprintf("https://ntfy.sh/awl_neuss_%s_err", channel) + _, err := http.Post(topic, "text/plain", strings.NewReader(message)) + return err +} + +func SendErr(channel string, err error) error { + topic := fmt.Sprintf("https://ntfy.sh/awl_neuss_%s_err", channel) + _, err = http.Post(topic, "text/plain", strings.NewReader(err.Error())) + return err +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..d4f21a8 --- /dev/null +++ b/shell.nix @@ -0,0 +1,24 @@ +{ pkgs ? ( + let + inherit (builtins) fetchTree fromJSON readFile; + inherit ((fromJSON (readFile ./flake.lock)).nodes) nixpkgs gomod2nix; + in + import (fetchTree nixpkgs.locked) { + overlays = [ + (import "${fetchTree gomod2nix.locked}/overlay.nix") + ]; + } + ) +, mkGoEnv ? pkgs.mkGoEnv +, gomod2nix ? pkgs.gomod2nix +}: + +let + goEnv = mkGoEnv { pwd = ./.; }; +in +pkgs.mkShell { + packages = [ + goEnv + gomod2nix + ]; +}