package main import ( "encoding/json" "errors" "fmt" "html/template" "io" "log" "net/http" "os" "sort" "strconv" "strings" ) type Favourite struct { Name string `json:"name"` Code string `json:"code"` } type Car struct { Time string `json:"time"` } type MaybeString string func (ms *MaybeString) UnmarshalJSON(b []byte) error { var i any err := json.Unmarshal(b, &i) if err != nil { return err } switch val := i.(type) { case int, float32, float64: *ms = MaybeString(fmt.Sprint(val)) return nil case string: *ms = MaybeString(val) return nil default: return fmt.Errorf("unknown type %T", val) } } type Line struct { Type string `json:"vehicle_type"` Name MaybeString `json:"name"` Cars []Car `json:"arrivals"` Color string } type SofiaTraffic struct { Name string `json:"name"` Code string `json:"code"` Lines []Line `json:"lines"` } func lessLine(left Line, right Line) bool { val := strings.Compare(left.Type, right.Type) if val != 0 { return val < 0 } else { l, lerr := strconv.Atoi(string(left.Name)) r, rerr := strconv.Atoi(string(right.Name)) switch { case lerr == nil && rerr == nil: return l < r case lerr == nil && rerr != nil: return true case lerr != nil && rerr == nil: return false case lerr != nil && rerr != nil: val = strings.Compare(string(left.Name), string(right.Name)) return val < 0 } } return true } func handle(resp http.ResponseWriter, req *http.Request) { id := strings.TrimPrefix(req.URL.Path, "/") find_id := req.URL.Query().Get("find") if len(find_id) != 0 { find_int, err := strconv.Atoi(find_id) if err != nil { log.Println(err) show500(resp) return } http.Redirect(resp, req, fmt.Sprintf("/%04d", find_int), http.StatusFound) } else if len(id) == 0 { showFavourites(resp) } else { showTable(resp, id) } } func show500(resp http.ResponseWriter) { resp.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(resp, "

500 Internal Server Error

") } func fixLine(line *Line) { switch line.Type { case "bus": line.Type = "А" case "trolley": line.Type = "ТБ" case "tram": line.Type = "ТМ" } switch line.Name[0] { case 'E': line.Type = "А" line.Name = line.Name[1:] } } func setLineColor(line *Line) { switch { case line.Type == "А": line.Color = "BD202E" case line.Type == "ТБ": line.Color = "2AA9E0" case line.Type == "ТМ": line.Color = "F7941F" default: line.Color = "000000" } } func getSofiaTrafficData(code string) (SofiaTraffic, error) { var data SofiaTraffic url := fmt.Sprintf("https://api-arrivals.sofiatraffic.bg/api/v1/arrivals/%s/", code) respAPI, err := http.Get(url) if err != nil { log.Println(err) return data, errors.New("get failed") } if respAPI.StatusCode != 200 { log.Printf("Status code for Code %v is %v", code, respAPI.StatusCode) return data, errors.New("bad status code") } if respAPI.Body != nil { defer respAPI.Body.Close() } body, err := io.ReadAll(respAPI.Body) if err != nil { log.Println(err) return data, errors.New("failed to read body") } err = json.Unmarshal(body, &data) if err != nil { log.Println(err) return data, errors.New("failed to unmarshal json") } return data, nil } func showTable(resp http.ResponseWriter, code string) { data, err := getSofiaTrafficData(code) if err != nil { fmt.Println("POOTIS") show500(resp) return } for idx := range(data.Lines) { // Fix Line fixLine(&(data.Lines[idx])) // Set Line Colors setLineColor(&(data.Lines[idx])) // Do not show seconds for car_idx := range(data.Lines[idx].Cars) { data.Lines[idx].Cars[car_idx].Time = data.Lines[idx].Cars[car_idx].Time[:5] } } // Sort Lines sort.Slice(data.Lines, func (i int, j int) bool { return lessLine(data.Lines[i], data.Lines[j]) }) templ := template.Must(template.ParseFiles("stop.html")) templ.Execute(resp, data) } func readFavourites() []Favourite { var favourites []Favourite jsonFile, err := os.Open("favourites.json") if err != nil { log.Println(err) return favourites } defer jsonFile.Close() bytes, err := io.ReadAll(jsonFile) if err != nil { log.Println(err) return favourites } err = json.Unmarshal(bytes, &favourites) if err != nil { log.Println(err) } return favourites } func showFavourites(resp http.ResponseWriter) { // Read And Parse JSON favourites := readFavourites() data := make(map[string] []Favourite) data["Favourites"] = favourites // Read And Execute Template templ := template.Must(template.ParseFiles("index.html")) templ.Execute(resp, data) } func main() { http.HandleFunc("/", handle) log.Println("Server Running") http.ListenAndServe(":8000", nil) }