package api import ( "encoding/json" "fmt" "io" "log" "net/http" "net/url" "sort" "strconv" "strings" "time" "github.com/gin-gonic/gin" ) const IDFM_BASEURL = "https://api-iv.iledefrance-mobilites.fr/" type IDFMRealTimeData struct { LineId string `json:"lineId"` ShortName string `json:"shortName"` VehicleName string `json:"vehicleName,omitempty"` LineDirection string `json:"lineDirection"` Sens string `json:"sens,omitempty"` Code string `json:"code,omitempty"` Time string `json:"time"` Schedule string `json:"schedule"` Destination struct { StopPointId string `json:"stopPointId"` StopAreaId string `json:"stopAreaId"` } `json:"destination,omitempty"` Source string `json:"source,omitempty"` } type IDFMRealTime struct { NextDepartures struct { StatusCode int `json:"statusCode"` ErrorMessage string `json:"errorMessage"` Data []IDFMRealTimeData `json:"data"` } `json:"nextDepartures"` CrowdsourcingReports struct { congestions []struct { DirectionId string `json:"directionId"` NearTimeReports struct { Rating *string `json:"rating"` } `json:"nearTimeReports"` } `json:"congestions"` } `json:"crowdsourcingReports"` } type ByRealTime []IDFMRealTimeData func (s ByRealTime) Len() int { return len(s) } func (s ByRealTime) Less(i, j int) bool { if s[i].Sens == s[j].Sens { nj, err := strconv.Atoi(s[j].Time) if err != nil { return false } ni, err := strconv.Atoi(s[i].Time) if err != nil { return true } return ni < nj } else { return s[i].Sens < s[j].Sens } } func (s ByRealTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] } type PGSchedule struct { Destination string `json:"destination"` Message string `json:"message"` Code string `json:"code,omitempty"` } 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 { v, _ := io.ReadAll(res.Body) log.Println("Schedule not found: ", string(v)) 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 } if schedules.NextDepartures.StatusCode >= 400 { log.Println("Schedule not found: ", schedules) return nil, fmt.Errorf("Schedule not found: %s", schedules.NextDepartures.ErrorMessage) } 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 := convertCode(t, string(c.Param("code"))) station := string(c.Param("station")) way := string(c.Param("way")) if !strings.HasPrefix(code, "line:IDFM:") { if len(code) != 6 || !strings.HasPrefix(code, "C") { code = searchLine(t, code) } code = "line:IDFM:" + code } if !strings.HasPrefix(station, "stop_area:IDFM:") { if _, err := strconv.ParseInt(station, 10, 64); err != nil { station, err = searchStation(code, station) if err != nil { c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) return } } else { station = "stop_area:IDFM:" + station } } log.Println("search", code, station) schedules, err := getRealTime(code, station) if err != nil { c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) return } sort.Sort(ByRealTime(schedules.NextDepartures.Data)) pgs := []PGSchedule{} for _, vehicule := range schedules.NextDepartures.Data { if (way == "A" && vehicule.Sens == "1") || (way == "R" && vehicule.Sens == "-1") { continue } msg := vehicule.Time + " mn" if vehicule.Code == "message" { msg = vehicule.Schedule } else if t == "rail" { if n, err := strconv.Atoi(vehicule.Time); err == nil { msg = time.Now().Add(time.Duration(n) * time.Minute).Format("15:04") } } pgs = append(pgs, PGSchedule{ Destination: vehicule.LineDirection, Message: msg, Code: vehicule.VehicleName, }) } c.JSON(http.StatusOK, APIResult(c, map[string][]PGSchedule{ "schedules": pgs, })) }) }