From a0659dd9bd5f46dd4773da3db015b8f98b9a540e Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Sun, 23 Oct 2022 00:56:24 +0200 Subject: [PATCH] Implement destinations --- api/destinations.go | 149 +++++++++++++++++++++++++++++++++++++++++++- api/schedules.go | 89 +++++++++++++------------- api/time.go | 17 +++++ 3 files changed, 211 insertions(+), 44 deletions(-) create mode 100644 api/time.go diff --git a/api/destinations.go b/api/destinations.go index ce44483..936861d 100644 --- a/api/destinations.go +++ b/api/destinations.go @@ -1,9 +1,154 @@ package api import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "strings" + "github.com/gin-gonic/gin" ) -func declareDestinationsRoutes(route *gin.RouterGroup) { - +type IDFMSchedule struct { + Line struct { + Id string `json:"id"` + Label string `json:"label"` + Name string `json:"name"` + ShortName string `json:"shortName"` + Companies []struct { + Id string `json:"id"` + Label string `json:"label"` + } `json:"companies"` + Network struct { + Id string `json:"id"` + Label string `json:"label"` + } `json:"network"` + Mode string `json:"mode"` + ModeLabel string `json:"modeLabel"` + Realtime bool `json:"realtime"` + UFR bool `json:"ufr"` + Visual bool `json:"visual"` + Sound bool `json:"sound"` + Color string `json:"color"` + TextColor string `json:"textColor"` + } `json:"line"` + Schedules []struct { + RouteId string `json:"routeId"` + From string `json:"from"` + To string `json:"to"` + First string `json:"first"` + Last string `json:"last"` + } `json:"schedules"` + Plans []struct { + Link string `json:"link"` + Label string `json:"label"` + } `json:"plans"` + ScheduleDocs []struct { + Link string `json:"link"` + Label string `json:"label"` + } `json:"scheduleDocs"` + CurrentIT []struct { + Id string `json:"id"` + Title string `json:"title"` + Message string `json:"message"` + ImpactStartTime IDFMTime `json:"impactStartTime"` + ImpactEndTime IDFMTime `json:"impactEndTime"` + Severity int `json:"severity"` + Type int `json:"type"` + } `json:"currentIT"` +} + +type PGDestination struct { + Name string `json:"name"` + Way string `json:"way"` +} + +func getSchedules(code string) (*IDFMSchedule, error) { + rurl, err := url.JoinPath(IDFM_BASEURL, "lines", code, "schedules") + if err != nil { + return nil, err + } + + requrl, err := url.Parse(rurl) + if err != nil { + return nil, err + } + + reqquery := url.Values{} + reqquery.Add("complete", "false") + requrl.RawQuery = reqquery.Encode() + + req, err := http.NewRequest("GET", requrl.String(), nil) + if err != nil { + return nil, err + } + + req.Header.Add("Accept", "application/json") + req.Header.Add("apikey", IDFM_TOKEN) + + res, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + + if res.StatusCode >= 400 { + return nil, fmt.Errorf("Schedule not found") + } + + var schedules IDFMSchedule + + dec := json.NewDecoder(res.Body) + if err = dec.Decode(&schedules); err != nil { + return nil, err + } + + return &schedules, nil +} + +func declareDestinationsRoutes(router *gin.RouterGroup) { + router.GET("/destinations/:type/:code", func(c *gin.Context) { + t := convertLineType(string(c.Param("type"))) + code := string(c.Param("code")) + + if !strings.HasPrefix(code, "line:IDFM:") { + if len(code) != 6 || !strings.HasPrefix(code, "C") { + code = searchLine(t, code) + } + + code = "line:IDFM:" + code + } + + schedule, err := getSchedules(code) + if err != nil { + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) + return + } + + pgd := []PGDestination{} + + destination: + for i, s := range schedule.Schedules { + for _, d := range pgd { + if d.Name == s.To { + continue destination + } + } + + way := "R" + if i%2 == 0 { + way = "A" + } + + pgd = append(pgd, PGDestination{ + Name: s.To, + Way: way, + }) + } + + c.JSON(http.StatusOK, APIResult(c, map[string][]PGDestination{ + "destinations": pgd, + })) + }) } diff --git a/api/schedules.go b/api/schedules.go index 0bfdbf7..91acb5c 100644 --- a/api/schedules.go +++ b/api/schedules.go @@ -2,6 +2,7 @@ package api import ( "encoding/json" + "fmt" "net/http" "net/url" "strconv" @@ -13,7 +14,7 @@ import ( const IDFM_BASEURL = "https://api-iv.iledefrance-mobilites.fr/" -type IDFMSchedule struct { +type IDFMRealTime struct { NextDepartures struct { StatusCode int `json:"statusCode"` Data []struct { @@ -52,10 +53,53 @@ func convertLineCode(code string) string { return strings.TrimSuffix(strings.Replace(code, "STIF:Line::", "IDFM:", 1), ":") } +func getRealTime(code, station string) (*IDFMRealTime, error) { + rurl, err := url.JoinPath(IDFM_BASEURL, "lines", code, "stops", station, "realTime") + if err != nil { + return nil, err + } + + requrl, err := url.Parse(rurl) + if err != nil { + return nil, err + } + + reqquery := url.Values{} + reqquery.Add("MonitoringRef", station) + requrl.RawQuery = reqquery.Encode() + + req, err := http.NewRequest("GET", requrl.String(), nil) + if err != nil { + return nil, err + } + + req.Header.Add("Accept", "application/json") + req.Header.Add("apikey", IDFM_TOKEN) + + res, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + + if res.StatusCode >= 400 { + return nil, fmt.Errorf("Schedule not found") + } + + var schedules IDFMRealTime + + dec := json.NewDecoder(res.Body) + if err = dec.Decode(&schedules); err != nil { + return nil, err + } + + return &schedules, nil +} + func declareSchedulesRoutes(router *gin.RouterGroup) { router.GET("/schedules/:type/:code/:station/:way", func(c *gin.Context) { t := convertLineType(string(c.Param("type"))) - code := convertLineType(string(c.Param("code"))) + code := string(c.Param("code")) station := string(c.Param("station")) way := string(c.Param("way")) @@ -79,51 +123,12 @@ func declareSchedulesRoutes(router *gin.RouterGroup) { } } - rurl, err := url.JoinPath(IDFM_BASEURL, "lines", code, "stops", station, "realTime") + schedules, err := getRealTime(code, station) if err != nil { c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) return } - requrl, err := url.Parse(rurl) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - reqquery := url.Values{} - reqquery.Add("MonitoringRef", station) - requrl.RawQuery = reqquery.Encode() - - req, err := http.NewRequest("GET", requrl.String(), nil) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - req.Header.Add("Accept", "application/json") - req.Header.Add("apikey", IDFM_TOKEN) - - res, err := http.DefaultClient.Do(req) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - defer res.Body.Close() - - if res.StatusCode >= 400 { - c.AbortWithStatusJSON(res.StatusCode, APIResult(c, nil)) - return - } - - var schedules IDFMSchedule - - dec := json.NewDecoder(res.Body) - if err = dec.Decode(&schedules); err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - pgs := []PGSchedule{} for _, vehicule := range schedules.NextDepartures.Data { if (way == "A" && vehicule.Sens == "-1") || (way == "R" && vehicule.Sens == "1") { diff --git a/api/time.go b/api/time.go new file mode 100644 index 0000000..2888cbc --- /dev/null +++ b/api/time.go @@ -0,0 +1,17 @@ +package api + +import ( + "time" +) + +type IDFMTime time.Time + +func (t *IDFMTime) UnmarshalJSON(b []byte) error { + tmp, err := time.Parse("\"2006-01-02T15:04\"", string(b)) + *t = IDFMTime(tmp) + return err +} + +func (t IDFMTime) MarshalJSON() ([]byte, error) { + return []byte(time.Time(t).Format("\"2006-01-02T15:04\"")), nil +}