sofiatraffic/main.go
2024-04-15 13:23:21 +03:00

252 lines
5.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"encoding/json"
"errors"
"fmt"
"html/template"
"io"
"log"
"net/http"
"os"
"sort"
"strconv"
"strings"
)
var stopIDs map[int] int;
type Favourite struct {
Name string `json:"name"`
Code string `json:"code"`
ID string `json:"id"`
}
type CarFlags struct {
Disabled bool `json:"disabled"`
AirConditioner bool `json:"klimatik"`
BicycleRack bool `json:"bicycle_rack"`
DoubleDecker bool `json:"doubledecker"`
ByTimeTable bool `json:"by_timetable"`
}
type Car struct {
DepartureTime string `json:"departure_time"`
Flags CarFlags `json:"flags"`
Destination int `json:"destination"`
CourseID int `json:"course_id"`
DestinationName string `json:"destination_name"`
CalculatedTime string `json:"calc_time"`
DirectionFlag int `json:"direction_flag"`
TTCourseStopID int `json:"ttcoursestop_id"`
}
type Line struct {
Transport string `json:"transport"`
ID int `json:"id"`
Name string `json:"name"`
Cars []Car `json:"cars"`
Color string
}
type VirtualPanelData struct {
ID int `json:"id"`
Lines []Line `json:"lines"`
}
type SofiaTraffic struct {
Data VirtualPanelData `json:"virtual_panel_data"`
}
type OutputData struct {
ID string
Lines []Line
}
type CacheData struct {
TargetID int `json:"target_id"`
DataID int `json:"data_id"`
}
func lessLine(left Line, right Line) bool {
val := strings.Compare(left.Transport, right.Transport)
if val != 0 {
return val < 0
} else {
l, lerr := strconv.Atoi(left.Name)
r, rerr := strconv.Atoi(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(left.Name, 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
}
target_id := stopIDs[find_int]
http.Redirect(resp, req, fmt.Sprintf("/%d", target_id), 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 setLineColor(line *Line) {
switch {
case line.Transport == "A":
line.Color = "FF0000"
case line.Transport == "ТБ":
line.Color = "0000FF"
case line.Transport == "ТМ":
line.Color = "FF8000"
}
}
func getSofiaTrafficData(id string) (SofiaTraffic, error) {
var data SofiaTraffic
url := fmt.Sprintf("https://www.sofiatraffic.bg/interactivecard/virtual_panel?stop_id=%s", id)
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 ID %v is %v", id, 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, id string) {
data, err := getSofiaTrafficData(id)
if err != nil {
show500(resp)
return
}
// Sort Lines
sort.Slice(data.Data.Lines, func (i int, j int) bool {
return lessLine(data.Data.Lines[i], data.Data.Lines[j])
})
// Set Line Colors
for idx := range(data.Data.Lines) {
setLineColor(&(data.Data.Lines[idx]))
}
var dataOut OutputData
dataOut.ID = fmt.Sprintf("%04d", data.Data.ID)
dataOut.Lines = data.Data.Lines
templ := template.Must(template.ParseFiles("stop.html"))
templ.Execute(resp, dataOut)
}
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 loadIDs() {
stopIDs = make(map[int]int)
var cache []CacheData
jsonFile, err := os.Open("cache.json")
if err != nil {
log.Println(err)
return
}
defer jsonFile.Close()
bytes, err := io.ReadAll(jsonFile)
if err != nil {
log.Println(err)
return
}
err = json.Unmarshal(bytes, &cache)
if err != nil {
log.Println(err)
return
}
for _, val := range(cache) {
stopIDs[val.TargetID] = val.DataID
}
}
func main() {
loadIDs()
http.HandleFunc("/", handle)
log.Println("Server Running")
http.ListenAndServe(":8000", nil)
}