From dceea169c29b05bf1a927ac25316271079e71084 Mon Sep 17 00:00:00 2001 From: Paul Brinkmeier Date: Wed, 17 Jul 2024 07:29:45 +0200 Subject: [PATCH] Init repo --- .gitignore | 3 ++ flake.lock | 27 +++++++++++ flake.nix | 19 ++++++++ go.mod | 3 ++ main.go | 140 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 192 insertions(+) create mode 100644 .gitignore create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 go.mod create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5e7cfbf --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +result +vrnp +*.swp diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..f1034b0 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1720957393, + "narHash": "sha256-oedh2RwpjEa+TNxhg5Je9Ch6d3W1NKi7DbRO1ziHemA=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "693bc46d169f5af9c992095736e82c3488bf7dbb", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..41625c4 --- /dev/null +++ b/flake.nix @@ -0,0 +1,19 @@ +{ + description = "vrnp"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + }; + + outputs = { self, nixpkgs }: + let + system = "x86_64-linux"; + pkgs = nixpkgs.legacyPackages.${system}; + in { + devShell.${system} = pkgs.mkShellNoCC { + packages = [ + pkgs.go + ]; + }; + }; +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..341c20a --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module pbrinkmeier/vrnp + +go 1.22.5 diff --git a/main.go b/main.go new file mode 100644 index 0000000..c134b64 --- /dev/null +++ b/main.go @@ -0,0 +1,140 @@ +package main + +import "encoding/json" +import "fmt" +import "log" +import "net/http" +import "net/url" +import "slices" +import "strconv" + +type DMResponse struct { + Departures []DMDeparture `json:"departureList"` +} + +type DMDeparture struct { + StopName string `json:"stopName"` + Platform string `json:"platform"` + Countdown string `json:"countdown"` + DateTime DMDateTime `json:"dateTime"` + RealDateTime *DMDateTime `json:"realDateTime"` + ServingLine DMServingLine `json:"servingLine"` + OnwardStopSeq []DMStop `json:"onwardStopSeq"` +} + +type DMServingLine struct { + Symbol string `json:"symbol"` + Direction string `json:"direction"` +} + +type DMStop struct { + PlaceID string `json:"placeID"` + Place string `json:"place"` + NameWO string `json:"nameWO"` +} + +type DMDateTime struct { + Hour string `json:"hour"` + Minute string `json:"minute"` +} + +// TODO: Use different client (with timeout) +func FetchDepartures(stopId string) (*DMResponse, error) { + // Create request object + req, err := http.NewRequest("GET", "https://www.vrn.de/mngvrn/XML_DM_REQUEST", nil) + if err != nil { + return nil, err + } + + // Configure our request + query := url.Values{} + query.Set("coordOutputFormat", "EPSG:4326") + query.Set("depType", "stopEvents") + query.Set("includeCompleteStopSeq", "1") + query.Set("limit", "10") + query.Set("locationServerActive", "0") + query.Set("mode", "direct") + query.Set("name_dm", stopId) + query.Set("outputFormat", "json") + query.Set("type_dm", "stop") + query.Set("useOnlyStops", "1") + query.Set("useRealtime", "1") + req.URL.RawQuery = query.Encode() + + // Send the request + res, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + + defer res.Body.Close() + var dmResponse DMResponse + err = json.NewDecoder(res.Body).Decode(&dmResponse) + if err != nil { + return nil, err + } + + return &dmResponse, nil +} + +func main() { + http.HandleFunc("/departures", func(w http.ResponseWriter, r *http.Request) { + query := r.URL.Query() + + if query["stop_id"] == nil || len(query["stop_id"]) < 1 { + http.Error(w, "Missing query parameter: stop_id", http.StatusBadRequest) + return + } + stopId := query["stop_id"][0] + + var platform *string = nil + if query["platform"] != nil && len(query["platform"]) != 0 { + platform = &query["platform"][0] + } + + + ds, err := FetchDepartures(stopId) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + for _, d := range ds.Departures { + if platform != nil { + if d.Platform != *platform { + continue + } + } + + direction := d.ServingLine.Direction + if len(d.OnwardStopSeq) != 0 { + last := d.OnwardStopSeq[len(d.OnwardStopSeq) - 1] + if slices.Contains([]string{"5", "6"}, last.PlaceID) { + direction = last.NameWO + } + } + + leaving := fmt.Sprintf("%s min", d.Countdown) + countdown, err := strconv.Atoi(d.Countdown) + if err == nil { + if countdown == 0 { + leaving = "sofort" + } + if countdown > 20 { + dt := d.DateTime + if d.RealDateTime != nil { + dt = *d.RealDateTime + } + leaving = fmt.Sprintf("%02s:%02s", dt.Hour, dt.Minute) + } + } + + fmt.Fprintf(w, "%2s %-22s %6s\n", + d.ServingLine.Symbol, + direction[:min(len(direction), 22)], + leaving, + ) + } + }) + log.Fatal(http.ListenAndServe(":8000", nil)) +}