penguin/golifehk

@golifehk bot in Telegram

commit 41be1db3818712c68c19761abfc37bd869e3e1eb

author斟酌 鵬兄 <tgckpg@gmail.com>
date2022-09-25T10:48:22Z
subjectAdded kmb schedules
commit 41be1db3818712c68c19761abfc37bd869e3e1eb
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date:   2022-09-25T10:48:22Z

    Added kmb schedules
---
 datasources/kmb/QueryResult.go     | 144 +++++++++++++++++++++++++++++++++----
 datasources/kmb/RouteStop.go       |  29 ++++++--
 datasources/kmb/query.go           |  21 +++++-
 datasources/kmb/query_test.go      |  15 ++++
 datasources/kmb/routestops.go      |  20 +++---
 datasources/kmb/routestops_test.go |   2 +-
 datasources/kmb/schedules.go       |  62 ++++++++++++++++
 datasources/mtr/bus/QueryResult.go |  26 ++++---
 8 files changed, 281 insertions(+), 38 deletions(-)

diff --git a/datasources/kmb/QueryResult.go b/datasources/kmb/QueryResult.go
index 8217bdd..3a9ee42 100644
--- a/datasources/kmb/QueryResult.go
+++ b/datasources/kmb/QueryResult.go
@@ -2,18 +2,52 @@ package kmb
 
 import (
     "fmt"
+    "sort"
     "strings"
+    "time"
 
     query "github.com/tgckpg/golifehk/query"
     utils "github.com/tgckpg/golifehk/utils"
 )
 
 type QueryResult struct {
+    Schedules *map[*RouteStop] *[] *Schedule
     Lang string
     Error error
     Query *query.QueryObject
 }
 
+func writeRouteHead( sb *strings.Builder, r *RouteStop ) {
+    utils.WriteMDv2Text( sb, r.RouteId )
+    if r.Direction == "O" {
+        sb.WriteString( "↑" )
+    } else if r.Direction == "I" {
+        sb.WriteString( "↓" )
+    }
+    if r.ServiceType != "1" {
+        utils.WriteMDv2Text( sb, utils.ToPower( r.ServiceType ) )
+    }
+}
+
+func writeShortRoute( lang *string, sb *strings.Builder, r *RouteStop ) {
+
+    if r.PrevStop() != nil {
+        utils.WriteMDv2Text( sb, (*(r.PrevStop().BusStop).Name)[ *lang ] )
+        sb.WriteString( " \\> " )
+    }
+
+    sb.WriteString( "*" )
+    utils.WriteMDv2Text( sb, (*(r.BusStop).Name)[ *lang ] )
+    sb.WriteString( "*" )
+
+    if r.NextStop() != nil {
+        sb.WriteString( " \\> " )
+        utils.WriteMDv2Text( sb, (*(r.NextStop().BusStop).Name)[ *lang ] )
+    }
+
+    sb.WriteString( "\n" )
+}
+
 func ( this *QueryResult ) Message() ( string, error ) {
 
     if this.Error != nil {
@@ -23,35 +57,119 @@ func ( this *QueryResult ) Message() ( string, error ) {
     sb := strings.Builder{}
 
     if 0 < len( *this.Query.Results ) {
-        // Print Stop Name, the print the list of routes
+
+        // Print Stop Names, then print the list of routes
         if this.Query.Key == "" {
+            busStops := map[string] *BusStop{}
             for _, item := range *this.Query.Results {
-                var b *BusStop
-                b = any( item ).( *BusStop )
+                var r *RouteStop
+                r = any( item ).( *RouteStop )
 
+                b := r.BusStop
                 if b.Routes == nil {
                     continue
                 }
 
+                busStops[ b.BusStopId ] = b
+            }
+
+            for _, b := range busStops {
                 utils.WriteMDv2Text( &sb, (*b.Name)[ this.Lang ] )
                 sb.WriteString( "\n  " )
                 for _, route := range *b.Routes {
-                    utils.WriteMDv2Text( &sb, route.RouteId )
-                    if route.Direction == "O" {
-                        sb.WriteString( "↑" )
-                    } else if route.Direction == "I" {
-                        sb.WriteString( "↓" )
-                    }
-                    if route.ServiceType != "1" {
-                        utils.WriteMDv2Text( &sb, utils.ToPower( route.ServiceType ) )
-                    }
+                    writeRouteHead( &sb, route )
                     sb.WriteString( " " )
                 }
                 sb.WriteString( "\n" )
             }
+
+        // We got a route key
+        } else {
+
+            // We also got other search keys with 1 < Results
+            // Get the ETA for this stop
+            if 1 < len( *this.Query.SearchTerms )  {
+
+                now := time.Now()
+                for _, item := range *this.Query.Results {
+                    var r *RouteStop
+                    r = any( item ).( *RouteStop )
+                    writeRouteHead( &sb, r )
+                    sb.WriteString( "\n" )
+                    writeShortRoute( &this.Lang, &sb, r )
+
+                    for _, schedule := range *(*this.Schedules)[ r ] {
+
+                        if !schedule.ETA.IsZero() {
+
+                            _m := schedule.ETA.Sub( now ).Minutes()
+
+                            sb.WriteString( "  \\* " )
+                            txt := "%.0f min(s)"
+
+                            if this.Lang == "zh-Hant" {
+                                txt = "%.0f 分鐘"
+                            }
+
+                            utils.WriteMDv2Text( &sb, fmt.Sprintf( txt, _m ) )
+
+                            if _m < 0 {
+                                sb.WriteString( " 走左了?" )
+                            }
+                        }
+
+                        if schedule.Remarks_en != "" {
+                            sb.WriteString( "  \\*\\* " )
+                            switch this.Lang {
+                            case "en":
+                                utils.WriteMDv2Text( &sb, schedule.Remarks_en )
+                            case "zh-Hant":
+                                utils.WriteMDv2Text( &sb, schedule.Remarks_tc )
+                            }
+                        }
+
+                        sb.WriteString( "\n" )
+                    }
+
+                    sb.WriteString( "\n" )
+                }
+
+            // We got only the route key, proceed to list the route stops
+            } else {
+                // Result contains all route stops, we only need the starting one
+                routes := [] *RouteStop{}
+
+                for _, item := range *this.Query.Results {
+                    var r *RouteStop
+                    r = any( item ).( *RouteStop )
+                    if r.PrevStop() == nil {
+                        routes = append( routes, r )
+                    }
+                }
+
+                sort.Sort( ByRoute( routes ) )
+
+                for _, r := range routes {
+                    writeRouteHead( &sb, r )
+                    sb.WriteString( "\n" )
+                    for {
+                        b := *r.BusStop
+                        utils.WriteMDv2Text( &sb, (*b.Name)[ this.Lang ] )
+                        r = r.NextStop()
+                        if r == nil {
+                            break
+                        }
+
+                        sb.WriteString( " \\> " )
+                    }
+                    sb.WriteString( "\n" )
+                }
+
+
+            }
         }
-        fmt.Print( this.Query.Key )
     } else {
+        return "", fmt.Errorf( "No Results" )
     }
 
     return sb.String(), nil
diff --git a/datasources/kmb/RouteStop.go b/datasources/kmb/RouteStop.go
index cde46a3..3a1a1f6 100644
--- a/datasources/kmb/RouteStop.go
+++ b/datasources/kmb/RouteStop.go
@@ -37,8 +37,29 @@ func ( routeStop RouteStop ) NextStop() *RouteStop {
     return nil
 }
 
-type ByRouteId []RouteStop
+func ( this *RouteStop ) Reload() {
 
-func (a ByRouteId) Len() int           { return len(a) }
-func (a ByRouteId) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
-func (a ByRouteId) Less(i, j int) bool { return a[i].RouteId < a[j].RouteId }
+    searchData := [] *string{}
+    busStop := *this.BusStop
+    searchData = append( searchData, &busStop.Name_en )
+    searchData = append( searchData, &busStop.Name_tc )
+
+    this.Key = &this.RouteId
+    this.SearchData = &searchData
+}
+
+type ByRoute [] *RouteStop
+
+func (a ByRoute) Len() int           { return len(a) }
+func (a ByRoute) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
+func (a ByRoute) Less(i, j int) bool {
+    _a := *a[i]
+    _b := *a[j]
+    if _a.RouteId == _b.RouteId {
+        if _a.Direction == _b.Direction {
+            return _a.ServiceType < _b.ServiceType
+        }
+        return _a.Direction < _b.Direction
+    }
+    return _a.RouteId < _b.RouteId
+}
diff --git a/datasources/kmb/query.go b/datasources/kmb/query.go
index e11ac16..5335339 100644
--- a/datasources/kmb/query.go
+++ b/datasources/kmb/query.go
@@ -11,13 +11,13 @@ func Query( lang string, message string ) query.IQueryResult {
     var err error
 
     qr := QueryResult{ Lang: lang }
-    busStops, err := getBusStops()
+    routeStops, err := getRouteStops()
     if err != nil {
         qr.Error = err
         goto qrReturn
     }
 
-    qo, err = query.Parse( strings.ToUpper( message ), busStops )
+    qo, err = query.Parse( strings.ToUpper( message ), routeStops )
     if err != nil {
         qr.Error = err
         goto qrReturn
@@ -25,6 +25,23 @@ func Query( lang string, message string ) query.IQueryResult {
 
     qr.Query = qo
 
+    if 0 < len( *qo.Results ) && 1 < len( *qo.SearchTerms ) {
+
+        rSchedules := map[*RouteStop] *[] *Schedule{}
+        for _, item := range *qo.Results {
+            var r *RouteStop
+            r = any( item ).( *RouteStop )
+            schedules, err := getSchedule( r )
+            if err != nil {
+                qr.Error = err
+                break
+            }
+
+            rSchedules[r] = schedules
+        }
+        qr.Schedules = &rSchedules
+    }
+
     qrReturn:
     var iqr query.IQueryResult
     iqr = &qr
diff --git a/datasources/kmb/query_test.go b/datasources/kmb/query_test.go
index e99628b..b832231 100644
--- a/datasources/kmb/query_test.go
+++ b/datasources/kmb/query_test.go
@@ -18,4 +18,19 @@ func TestQuerySchedule( t *testing.T ) {
     if err == nil {
         t.Errorf( "Expected Error: %s, got \"\" instead", mesg )
     }
+
+    qo = Query( "zh-Hant", "大欖" )
+    mesg, err = qo.Message()
+    if err != nil {
+        t.Errorf( "Unexpected Error: %s", err )
+    }
+
+    fmt.Println( mesg )
+
+    qo = Query( "zh-Hant", "261B 大欖" )
+    mesg, err = qo.Message()
+    if err != nil {
+        t.Errorf( "Unexpected Error: %s", err )
+    }
+    fmt.Println( mesg )
 }
diff --git a/datasources/kmb/routestops.go b/datasources/kmb/routestops.go
index 4b85e4e..26b97f4 100644
--- a/datasources/kmb/routestops.go
+++ b/datasources/kmb/routestops.go
@@ -16,16 +16,18 @@ import (
 var JSON_ROUTESTOPS string = filepath.Join( utils.WORKDIR, "kmb-routestops.json" )
 var JSON_BUSSTOPS string = filepath.Join( utils.WORKDIR, "kmb-busstops.json" )
 
-func readRouteStopsData( busStops *map[string] *BusStop, buff *bytes.Buffer ) error {
+func readRouteStopsData( busStops *map[string] *BusStop, buff *bytes.Buffer ) ( *[] *RouteStop, error ) {
 
     routeStopsData := RouteStops{}
     err := json.Unmarshal( buff.Bytes(), &routeStopsData )
     if err != nil {
-        return err
+        return nil, err
     }
 
     // routeStops[ Route ][ ServiceType ][ Direction ][ Seq ] = RouteStop
     routeStops := map[string] *map[string] *map[ string ] *map[ int ] *RouteStop{}
+    allRouteStops := [] *RouteStop{}
+
     for _, entry := range routeStopsData.RouteStops {
 
         busStop := (*busStops)[ entry.StationId ]
@@ -63,16 +65,17 @@ func readRouteStopsData( busStops *map[string] *BusStop, buff *bytes.Buffer ) er
         if direction == nil {
             direction = &map[ int ] *RouteStop{}
             (*service)[ entry.Direction ] = direction
-            entry.RouteStops = direction
         }
+        entry.RouteStops = direction
 
         seq := (*direction)[ entry.StationSeq ]
         if seq == nil {
             (*direction)[ entry.StationSeq ] = entry
         }
+        allRouteStops = append( allRouteStops, entry )
     }
 
-    return nil
+    return &allRouteStops, nil
 }
 
 func readBusStopsData( buff *bytes.Buffer ) ( *map[string]*BusStop, error ) {
@@ -96,7 +99,7 @@ func readBusStopsData( buff *bytes.Buffer ) ( *map[string]*BusStop, error ) {
     return &busStopMap, nil
 }
 
-func getBusStops() (*[] query.ISearchable, error) {
+func getRouteStops() (*[] query.ISearchable, error) {
 
     QUERY_FUNC := func() ( io.ReadCloser, error ) {
         resp, err := http.Get( "https://data.etabus.gov.hk/v1/transport/kmb/stop" )
@@ -129,14 +132,15 @@ func getBusStops() (*[] query.ISearchable, error) {
         return nil, err
     }
 
-    err = readRouteStopsData( busStopMap, buff )
+    routeStops, err := readRouteStopsData( busStopMap, buff )
     if err != nil {
         return nil, err
     }
 
     searchables := [] query.ISearchable{}
-    for _, busStop := range *busStopMap {
-        searchables = append( searchables, busStop )
+    for _, routeStop := range *routeStops {
+        searchables = append( searchables, routeStop )
+        routeStop.Reload()
     }
 
     return &searchables, nil
diff --git a/datasources/kmb/routestops_test.go b/datasources/kmb/routestops_test.go
index 399ca2b..cd2ccf8 100644
--- a/datasources/kmb/routestops_test.go
+++ b/datasources/kmb/routestops_test.go
@@ -5,7 +5,7 @@ import (
 )
 
 func TestRouteStops(t *testing.T) {
-    _, err := getBusStops()
+    _, err := getRouteStops()
     if err != nil {
         t.Error( err )
     }
diff --git a/datasources/kmb/schedules.go b/datasources/kmb/schedules.go
new file mode 100644
index 0000000..97726c6
--- /dev/null
+++ b/datasources/kmb/schedules.go
@@ -0,0 +1,62 @@
+package kmb
+
+import (
+    "encoding/json"
+    "fmt"
+    "io"
+    "net/http"
+    "path/filepath"
+    "time"
+
+    "github.com/tgckpg/golifehk/utils"
+)
+
+type Schedule struct {
+    ETA time.Time `json:"eta,string"`
+    Index int `json:"eta_seq"`
+    DateCreated time.Time `json:"data_timestamp,string"`
+    Remarks_en string `json:"rmk_en"`
+    Remarks_sc string `json:"rmk_sc"`
+    Remarks_tc string `json:"rmk_tc"`
+}
+
+type Schedules struct {
+    Type string `json:"type"`
+    Version string `json:"version"`
+    DateCreated time.Time `json:"generated_timestamp,string"`
+    Schedules [] *Schedule `json:"data"`
+}
+
+func getSchedule( r *RouteStop ) ( *[] *Schedule, error ) {
+
+    CACHE_PATH := filepath.Join(
+        utils.WORKDIR,
+        "kmb-" + r.BusStop.BusStopId + "-" + r.RouteId + "-" + r.ServiceType + ".json",
+    )
+
+    QUERY_FUNC := func() ( io.ReadCloser, error ) {
+        resp, err := http.Get( fmt.Sprintf(
+                "https://data.etabus.gov.hk/v1/transport/kmb/eta/%s/%s/%s",
+                r.BusStop.BusStopId,
+                r.RouteId,
+                r.ServiceType,
+        ) )
+        if err != nil {
+            return nil, err
+        }
+        return resp.Body, nil
+    }
+
+    buff, err := utils.CacheStream( CACHE_PATH, QUERY_FUNC, 60 )
+    if err != nil {
+        return nil, err
+    }
+
+    schedules := Schedules{}
+    err = json.Unmarshal( buff.Bytes(), &schedules )
+    if err != nil {
+        return nil, err
+    }
+
+    return &schedules.Schedules, nil
+}
diff --git a/datasources/mtr/bus/QueryResult.go b/datasources/mtr/bus/QueryResult.go
index dc7db64..321c1d4 100644
--- a/datasources/mtr/bus/QueryResult.go
+++ b/datasources/mtr/bus/QueryResult.go
@@ -18,17 +18,17 @@ type QueryResult struct {
 
 func writeShortRoute( lang *string, sb *strings.Builder, b *BusStop ) {
     if b.PrevStop() != nil {
-        utils.WriteMDv2Text( sb, (*b.PrevStop().Name)[ *lang ] ) 
+        utils.WriteMDv2Text( sb, (*b.PrevStop().Name)[ *lang ] )
         sb.WriteString( " \\> " )
     }
 
     sb.WriteString( "*" )
-    utils.WriteMDv2Text( sb, (*b.Name)[ *lang ] ) 
+    utils.WriteMDv2Text( sb, (*b.Name)[ *lang ] )
     sb.WriteString( "*" )
 
     if b.NextStop() != nil {
         sb.WriteString( " \\> " )
-        utils.WriteMDv2Text( sb, (*b.NextStop().Name)[ *lang ] ) 
+        utils.WriteMDv2Text( sb, (*b.NextStop().Name)[ *lang ] )
     }
 
     sb.WriteString( "\n" )
@@ -49,7 +49,7 @@ func ( this QueryResult ) Message() ( string, error ) {
             sort.Sort( query.ByKey( *q.Results ) )
             for _, entry := range *q.Results {
                 busStop := any( entry ).( *BusStop )
-                utils.WriteMDv2Text( &sb, busStop.RouteId ) 
+                utils.WriteMDv2Text( &sb, busStop.RouteId )
                 sb.WriteString( " " )
                 writeShortRoute( &this.Lang, &sb, busStop )
             }
@@ -79,12 +79,18 @@ func ( this QueryResult ) Message() ( string, error ) {
             for _, d := range keys {
                 b := st[ d ]
                 sb.WriteString( q.Key )
-                sb.WriteString( "\\-" )
-                utils.WriteMDv2Text( &sb, d ) 
+
+                if d == "O" {
+                    sb.WriteString( "↑" )
+                } else if d == "I" {
+                    sb.WriteString( "↓" )
+                } else {
+                    sb.WriteString( "\\?" )
+                }
                 sb.WriteString( "\n " )
 
                 for {
-                    utils.WriteMDv2Text( &sb, (*b.Name)[ this.Lang ] ) 
+                    utils.WriteMDv2Text( &sb, (*b.Name)[ this.Lang ] )
                     b = b.NextStop()
                     if b == nil {
                         break
@@ -111,16 +117,16 @@ func ( this QueryResult ) Message() ( string, error ) {
                 for _, bus := range buses.Buses {
                     sb.WriteString( "  \\* " )
                     if bus.ETAText == "" {
-                        utils.WriteMDv2Text( &sb, bus.ETDText ) 
+                        utils.WriteMDv2Text( &sb, bus.ETDText )
                     } else {
-                        utils.WriteMDv2Text( &sb, bus.ETAText ) 
+                        utils.WriteMDv2Text( &sb, bus.ETAText )
                     }
                     sb.WriteString( "\n" )
                 }
                 sb.WriteString( "\n" )
             }
         } else {
-            utils.WriteMDv2Text( &sb, "Schedules are empty...perhaps Out of Service Time?" ) 
+            utils.WriteMDv2Text( &sb, "Schedules are empty...perhaps Out of Service Time?" )
         }
     }