236 lines
4.6 KiB
Go
236 lines
4.6 KiB
Go
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, "<h1 style=\"text-align:center;\">500 Internal Server Error</h1>")
|
||
}
|
||
|
||
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)
|
||
}
|