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, "
500 Internal Server Error
")
}
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)
}