Added kmb and refactored query.Parse

This commit is contained in:
斟酌 鵬兄 2022-09-17 04:33:47 +08:00
parent 292665c49b
commit 3376d9eb96
21 changed files with 762 additions and 241 deletions

View File

@ -0,0 +1,40 @@
package kmb
import (
i18n "github.com/tgckpg/golifehk/i18n"
)
type BusStop struct {
BusStopId string `json:"stop"`
Latitude float64 `json:"lat,string"`
Longtitude float64 `json:"long,string"`
Name_en string `json:"name_en"`
Name_tc string `json:"name_tc"`
Name_sc string `json:"name_sc"`
// Routes[ Route ][ Direction ]
Routes *[] *RouteStop
i18n.Generics
}
type BusStops struct {
Type string `json:"type"`
Version string `json:"version"`
DateCreated string `json:"generated_timestamp"`
BusStops [] *BusStop `json:"data"`
}
func ( this *BusStop ) Reload() {
i18n_Name := map[string] string{}
i18n_Name["en"] = this.Name_en
i18n_Name["zh-Hant"] = this.Name_tc
searchData := [] *string{}
searchData = append( searchData, &this.Name_en )
searchData = append( searchData, &this.Name_tc )
this.Name = &i18n_Name
this.Key = &this.BusStopId
this.SearchData = &searchData
}

View File

@ -0,0 +1,47 @@
package kmb
import (
"strings"
query "github.com/tgckpg/golifehk/query"
utils "github.com/tgckpg/golifehk/utils"
)
type QueryResult struct {
Lang string
Error error
Query *query.QueryObject
}
func ( this *QueryResult ) Message() ( string, error ) {
if this.Error != nil {
return "", this.Error
}
sb := strings.Builder{}
if 0 < len( *this.Query.Results ) {
// Print Stop Name, the print the list of routes
if this.Query.Key == "" {
for _, item := range *this.Query.Results {
var b *BusStop
b = any( item ).( *BusStop )
if b.Routes == nil {
continue
}
utils.WriteMDv2Text( &sb, (*b.Name)[ this.Lang ] )
sb.WriteString( "\n " )
for _, route := range *b.Routes {
utils.WriteMDv2Text( &sb, route.RouteId )
sb.WriteString( " " )
}
sb.WriteString( "\n" )
}
}
}
return sb.String(), nil
}

View File

@ -0,0 +1,44 @@
package kmb
import (
query "github.com/tgckpg/golifehk/query"
)
type RouteStop struct {
BusStop *BusStop
RouteId string `json:"route"`
ServiceType string `json:"service_type"`
Direction string `json:"bound"`
StationSeq int `json:"seq,string"`
StationId string `json:"stop"`
RouteStops *map[int] *RouteStop
query.Searchable
}
type RouteStops struct {
Type string `json:"type"`
Version string `json:"version"`
DateCreated string `json:"generated_timestamp"`
RouteStops [] *RouteStop `json:"data"`
}
func ( routeStop RouteStop ) PrevStop() *RouteStop {
if v, hasKey := (*routeStop.RouteStops)[ routeStop.StationSeq - 1 ]; hasKey {
return v
}
return nil
}
func ( routeStop RouteStop ) NextStop() *RouteStop {
if v, hasKey := (*routeStop.RouteStops)[ routeStop.StationSeq + 1 ]; hasKey {
return v
}
return nil
}
type ByRouteId []RouteStop
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 }

32
datasources/kmb/query.go Normal file
View File

@ -0,0 +1,32 @@
package kmb
import (
"strings"
query "github.com/tgckpg/golifehk/query"
)
func Query( lang string, message string ) query.IQueryResult {
var qo *query.QueryObject
var err error
qr := QueryResult{ Lang: lang }
busStops, err := getBusStops()
if err != nil {
qr.Error = err
goto qrReturn
}
qo, err = query.Parse( strings.ToUpper( message ), busStops )
if err != nil {
qr.Error = err
goto qrReturn
}
qr.Query = qo
qrReturn:
var iqr query.IQueryResult
iqr = &qr
return iqr
}

View File

@ -0,0 +1,10 @@
package kmb
import (
"fmt"
"testing"
)
func TestQuerySchedule( t *testing.T ) {
fmt.Print( Query( "zh", "大欖" ).Message() )
}

View File

@ -0,0 +1,143 @@
package kmb
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"path/filepath"
// "strings"
query "github.com/tgckpg/golifehk/query"
"github.com/tgckpg/golifehk/utils"
)
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 {
routeStopsData := RouteStops{}
err := json.Unmarshal( buff.Bytes(), &routeStopsData )
if err != nil {
return err
}
// routeStops[ Route ][ ServiceType ][ Direction ][ Seq ] = RouteStop
routeStops := map[string] *map[string] *map[ string ] *map[ int ] *RouteStop{}
for _, entry := range routeStopsData.RouteStops {
busStop := (*busStops)[ entry.StationId ]
if busStop == nil {
busStop = &BusStop {
BusStopId: entry.StationId,
Name_en: "???", Name_tc: "???", Name_sc: "???",
}
busStop.Reload()
(*busStops)[ entry.StationId ] = busStop
}
if busStop.Routes == nil {
busStopRoutes := [] *RouteStop{}
busStop.Routes = &busStopRoutes
}
(*busStop.Routes) = append( (*busStop.Routes), entry )
entry.BusStop = busStop
route := routeStops[ entry.RouteId ]
if route == nil {
route = &map[string] *map[ string ] *map[ int ] *RouteStop{}
routeStops[ entry.RouteId ] = route
}
service := (*route)[ entry.ServiceType ]
if service == nil {
service = &map[ string ] *map[ int ] *RouteStop{}
(*route)[ entry.ServiceType ] = service
}
direction := (*service)[ entry.Direction ]
if direction == nil {
direction = &map[ int ] *RouteStop{}
(*service)[ entry.Direction ] = direction
entry.RouteStops = direction
}
seq := (*direction)[ entry.StationSeq ]
if seq == nil {
(*direction)[ entry.StationSeq ] = entry
}
}
return nil
}
func readBusStopsData( buff *bytes.Buffer ) ( *map[string]*BusStop, error ) {
busStopsData := BusStops{}
err := json.Unmarshal( buff.Bytes(), &busStopsData )
if err != nil {
return nil, err
}
busStopMap := map[string] *BusStop{}
for _, entry := range busStopsData.BusStops {
entry.Reload()
if _, ok := busStopMap[ entry.BusStopId ]; ok {
return nil, fmt.Errorf( "Duplicated BusStop: %s", entry.BusStopId )
}
busStopMap[ entry.BusStopId ] = entry
}
return &busStopMap, nil
}
func getBusStops() (*[] query.ISearchable, error) {
QUERY_FUNC := func() ( io.ReadCloser, error ) {
resp, err := http.Get( "https://data.etabus.gov.hk/v1/transport/kmb/stop" )
if err != nil {
return nil, err
}
return resp.Body, nil
}
buff, err := utils.CacheStream( JSON_BUSSTOPS, QUERY_FUNC, 7 * 24 * 3600 )
if err != nil {
return nil, err
}
busStopMap, err := readBusStopsData( buff )
if err != nil {
return nil, err
}
QUERY_FUNC = func() ( io.ReadCloser, error ) {
resp, err := http.Get( "https://data.etabus.gov.hk/v1/transport/kmb/route-stop" )
if err != nil {
return nil, err
}
return resp.Body, nil
}
buff, err = utils.CacheStream( JSON_ROUTESTOPS, QUERY_FUNC, 7 * 24 * 3600 )
if err != nil {
return nil, err
}
err = readRouteStopsData( busStopMap, buff )
if err != nil {
return nil, err
}
searchables := [] query.ISearchable{}
for _, busStop := range *busStopMap {
searchables = append( searchables, busStop )
}
return &searchables, nil
}

View File

@ -0,0 +1,12 @@
package kmb
import (
"testing"
)
func TestRouteStops(t *testing.T) {
_, err := getBusStops()
if err != nil {
t.Error( err )
}
}

View File

@ -1,5 +1,9 @@
package bus
import (
i18n "github.com/tgckpg/golifehk/i18n"
)
type BusStop struct {
RouteId string
Direction string
@ -7,27 +11,39 @@ type BusStop struct {
StationId string
Latitude float64
Longtitude float64
Name_zh string
Name_en string
Name *map[string] string
// RouteStops[ StationSeq ] = BusStop
RouteStops *map[int] *BusStop
i18n.Generics
}
func ( busStop BusStop ) PrevStop() *BusStop {
if v, hasKey := (*busStop.RouteStops)[ busStop.StationSeq - 1 ]; hasKey {
func ( this *BusStop ) PrevStop() *BusStop {
if v, hasKey := (*this.RouteStops)[ this.StationSeq - 1 ]; hasKey {
return v
}
return nil
}
func ( busStop BusStop ) NextStop() *BusStop {
if v, hasKey := (*busStop.RouteStops)[ busStop.StationSeq + 1 ]; hasKey {
func ( this *BusStop ) NextStop() *BusStop {
if v, hasKey := (*this.RouteStops)[ this.StationSeq + 1 ]; hasKey {
return v
}
return nil
}
type ByRouteId []BusStop
func ( this *BusStop ) Reload() {
i18n_Name := map[string] string{}
i18n_Name["en"] = this.Name_en
i18n_Name["zh-Hant"] = this.Name_zh
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{}
searchData = append( searchData, &this.Name_en )
searchData = append( searchData, &this.Name_zh )
this.Name = &i18n_Name
this.Key = &this.RouteId
this.SearchData = &searchData
}

View File

@ -4,68 +4,87 @@ import (
"fmt"
"sort"
"strings"
query "github.com/tgckpg/golifehk/query"
utils "github.com/tgckpg/golifehk/utils"
)
type QueryResult struct {
Schedules *map[BusStop] *BusStopBuses
Schedules *map[*BusStop] *BusStopBuses
Lang string
Error error
Query *QueryObject
}
var MARKDOWN_ESC []string = []string{"_", "*", "[", "]", "(", ")", "~", "`", ">", "#", "+", "-", "=", "|", "{", "}", ".", "!"}
func _escape( t string ) string {
for _, c := range MARKDOWN_ESC {
t = strings.Replace( t, c, "\\" + c, -1 )
}
return t
Query *query.QueryObject
}
func writeShortRoute( lang *string, sb *strings.Builder, b *BusStop ) {
if b.PrevStop() != nil {
sb.WriteString( _escape( (*b.PrevStop().Name)[ *lang ] ) )
utils.WriteMDv2Text( sb, (*b.PrevStop().Name)[ *lang ] )
sb.WriteString( " \\> " )
}
sb.WriteString( "*" )
sb.WriteString( _escape( (*b.Name)[ *lang ] ) )
utils.WriteMDv2Text( sb, (*b.Name)[ *lang ] )
sb.WriteString( "*" )
if b.NextStop() != nil {
sb.WriteString( " \\> " )
sb.WriteString( _escape( (*b.NextStop().Name)[ *lang ] ) )
utils.WriteMDv2Text( sb, (*b.NextStop().Name)[ *lang ] )
}
sb.WriteString( "\n" )
}
func ( result QueryResult ) Message() string {
func ( this QueryResult ) Message() ( string, error ) {
if result.Error != nil {
return fmt.Sprintf( "%s", result.Error )
if this.Error != nil {
return "", this.Error
}
sb := strings.Builder{}
if result.Schedules == nil {
if this.Schedules == nil {
query := *result.Query
if query.Route == "" {
sort.Sort( ByRouteId( *query.BusStops ) )
for _, busStop := range *query.BusStops {
sb.WriteString( _escape( busStop.RouteId ) )
q := *this.Query
if q.Key == "" {
sort.Sort( query.ByKey( *q.Results ) )
for _, entry := range *q.Results {
busStop := any( entry ).( *BusStop )
utils.WriteMDv2Text( &sb, busStop.RouteId )
sb.WriteString( " " )
writeShortRoute( &result.Lang, &sb, &busStop )
writeShortRoute( &this.Lang, &sb, busStop )
}
} else if query.BusStops == nil && query.RouteStarts != nil {
for d, b := range *query.RouteStarts {
sb.WriteString( query.Route )
} else if 1 == len( *q.SearchTerms ) && 0 < len( *q.Results ) {
// Route listing
st := map[string] *BusStop{}
keys := [] string{}
for _, entry := range *q.Results {
busStop := any( entry ).( *BusStop )
if _, ok := st[ busStop.Direction ]; ok {
continue
}
st[ busStop.Direction ] = busStop
keys = append( keys, busStop.Direction )
for st[ busStop.Direction ].PrevStop() != nil {
st[ busStop.Direction ] = st[ busStop.Direction ].PrevStop()
}
}
sort.Strings( keys )
for _, d := range keys {
b := st[ d ]
sb.WriteString( q.Key )
sb.WriteString( "\\-" )
sb.WriteString( _escape( d ) )
utils.WriteMDv2Text( &sb, d )
sb.WriteString( "\n " )
for {
sb.WriteString( _escape( (*b.Name)[ result.Lang ] ) )
utils.WriteMDv2Text( &sb, (*b.Name)[ this.Lang ] )
b = b.NextStop()
if b == nil {
break
@ -75,28 +94,35 @@ func ( result QueryResult ) Message() string {
}
sb.WriteString( "\n" )
}
} else if 1 < len( *q.SearchTerms ) && len( *q.Results ) == 0 {
terms := make( []string, len(*q.SearchTerms), len(*q.SearchTerms) )
for i, term := range *q.SearchTerms {
terms[i] = term.Org
}
return "", fmt.Errorf( "Not Found: \"%s\"", strings.Join( terms, "\", \"" ) )
} else {
sb.WriteString( _escape( "Unreachable condition occured!?" ) )
return "", fmt.Errorf( "%s", "Unreachable condition occured!?" )
}
} else {
if 0 < len(*result.Schedules) {
for busStop, buses := range *result.Schedules {
writeShortRoute( &result.Lang, &sb, &busStop )
if 0 < len( *this.Schedules ) {
for busStop, buses := range *this.Schedules {
writeShortRoute( &this.Lang, &sb, busStop )
for _, bus := range buses.Buses {
sb.WriteString( " \\* " )
if bus.ETAText == "" {
sb.WriteString( _escape( bus.ETDText ) )
utils.WriteMDv2Text( &sb, bus.ETDText )
} else {
sb.WriteString( _escape( bus.ETAText ) )
utils.WriteMDv2Text( &sb, bus.ETAText )
}
sb.WriteString( "\n" )
}
sb.WriteString( "\n" )
}
} else {
sb.WriteString( _escape( "Schedules are empty...perhaps Out of Service Time?" ) )
utils.WriteMDv2Text( &sb, "Schedules are empty...perhaps Out of Service Time?" )
}
}
return sb.String()
return sb.String(), nil
}

View File

@ -45,9 +45,15 @@ func getSchedule( lang string, routeName string ) ( *BusSchedule, error ) {
"mtr_bsch" + "-" + lang + "-" + routeName + ".json",
)
postLang := "en"
if lang == "zh-Hant" {
postLang = "zh"
}
QUERY_FUNC := func() ( io.ReadCloser, error ) {
// Query Remote
values := map[string]string { "language": lang , "routeName": routeName }
values := map[string]string { "language": postLang, "routeName": routeName }
jsonValue, _ := json.Marshal(values)
resp, err := http.Post(
"https://rt.data.gov.hk/v1/transport/mtr/bus/getSchedule",

View File

@ -9,12 +9,13 @@ import (
"strconv"
"strings"
query "github.com/tgckpg/golifehk/query"
"github.com/tgckpg/golifehk/utils"
)
var CSV_BUSSTOPS string = filepath.Join( utils.WORKDIR, "mtr_bus_stops.csv" )
func readBusStopData( r io.Reader ) ( *map[string]BusStop, error ) {
func readBusStopData( r io.Reader ) ( *map[string] *BusStop, error ) {
reader := csv.NewReader( r )
entries, err := reader.ReadAll()
@ -22,7 +23,7 @@ func readBusStopData( r io.Reader ) ( *map[string]BusStop, error ) {
return nil, err
}
busStops := map[string]BusStop{}
busStops := map[string] *BusStop{}
routeStops := map[string] map[string] map[int] *BusStop{}
var headers []string
@ -34,7 +35,6 @@ func readBusStopData( r io.Reader ) ( *map[string]BusStop, error ) {
}
var entry BusStop
i18n_Name := map[string] string{}
for j, value := range line {
switch headers[j] {
@ -54,15 +54,15 @@ func readBusStopData( r io.Reader ) ( *map[string]BusStop, error ) {
v, _ := strconv.ParseFloat( value, 64 )
entry.Longtitude = v
case "STATION_NAME_CHI":
i18n_Name["zh"] = value
entry.Name_zh = value
case "STATION_NAME_ENG":
i18n_Name["en"] = value
entry.Name_en = value
default:
return nil, fmt.Errorf( "Unknown header \"%s\"", headers[j] )
}
}
if _, t := busStops[ entry.StationId ]; t {
if busStops[ entry.StationId ] != nil {
return nil, fmt.Errorf( "Duplicated entry %+v", entry )
}
@ -83,15 +83,15 @@ func readBusStopData( r io.Reader ) ( *map[string]BusStop, error ) {
route[ entry.StationSeq ] = &entry
}
entry.Name = &i18n_Name
entry.RouteStops = &route
entry.Reload()
busStops[ entry.StationId ] = entry
busStops[ entry.StationId ] = &entry
}
return &busStops, nil
}
func getBusStops() (*map[string]BusStop, error) {
func getBusStops() (*[] query.ISearchable, error) {
QUERY_FUNC := func() ( io.ReadCloser, error ) {
resp, err := http.Get( "https://opendata.mtr.com.hk/data/mtr_bus_stops.csv" )
@ -106,5 +106,16 @@ func getBusStops() (*map[string]BusStop, error) {
return nil, err
}
return readBusStopData( buff )
busStopMap, err := readBusStopData( buff )
if err != nil {
return nil, err
}
searchables := [] query.ISearchable{}
for _, busStop := range *busStopMap {
searchables = append( searchables, busStop )
}
return &searchables, nil
}

View File

@ -1,76 +1,44 @@
package bus
import (
"errors"
"fmt"
"strings"
"github.com/tgckpg/golifehk/utils"
query "github.com/tgckpg/golifehk/query"
)
type qTerm struct {
Org string
Value string
ProblyRoute bool
}
func Query( lang string, message string ) query.IQueryResult {
type QueryObject struct {
Route string
BusStops *[]BusStop
RouteStarts *map[string] *BusStop
SearchTerms *[] *qTerm
}
func NotFound ( terms *[] *qTerm ) string {
sb := strings.Builder{}
sb.WriteString( "Not Found" )
if 0 < len( *terms ) {
sb.WriteString( ":" )
for _, term := range *terms {
sb.WriteString( " \"" )
sb.WriteString( term.Org )
sb.WriteString( "\"" )
}
}
return sb.String()
}
func Query( lang string, message string ) *QueryResult {
var qBusStops *query.QueryObject
var err error
qr := QueryResult{ Lang: lang }
qo, err := parse( message )
busStops, err := getBusStops()
if err != nil {
qr.Error = err
return &qr
goto qrReturn
}
qr.Query = qo
qBusStops, err = query.Parse( strings.ToUpper( message ), busStops )
if err != nil {
qr.Error = err
goto qrReturn
}
if qo.Route != "" {
if qo.BusStops == nil {
return &qr
}
if len( *qo.BusStops ) == 0 {
qr.Error = errors.New( NotFound( qo.SearchTerms ) )
return &qr
}
schedules, err := getSchedule( lang, qo.Route )
qr.Query = qBusStops
if 0 < len( *qBusStops.Results ) && 1 < len( *qBusStops.SearchTerms ) {
schedules, err := getSchedule( lang, qBusStops.Key )
if err != nil {
qr.Error = err
return &qr
goto qrReturn
}
if len( schedules.BusStops ) == 0 {
qr.Schedules = &map[BusStop] *BusStopBuses{}
return &qr
qr.Schedules = &map[*BusStop] *BusStopBuses{}
goto qrReturn
}
matches := map[BusStop] *BusStopBuses{}
for _, busStop := range *qo.BusStops {
matches := map[*BusStop] *BusStopBuses{}
for _, entry := range *qBusStops.Results {
busStop := any( entry ).( *BusStop )
for _, busStopSch := range schedules.BusStops {
if busStopSch.BusStopId == busStop.StationId {
@ -82,131 +50,10 @@ func Query( lang string, message string ) *QueryResult {
}
qr.Schedules = &matches
return &qr
} else {
if len( *qo.BusStops ) == 0 {
return &QueryResult{ Error: errors.New( NotFound( qo.SearchTerms ) ) }
}
return &qr
}
}
func test( entry BusStop, val string ) bool {
switch true {
case strings.Contains( strings.ToUpper( (*entry.Name)["zh"] ), val ):
fallthrough
case strings.Contains( strings.ToUpper( (*entry.Name)["en"] ), val ):
return true
}
return false
}
func parse( line string ) ( *QueryObject, error ) {
busStops, err := getBusStops()
if err != nil {
return nil, err
}
var route string = ""
matches := []BusStop{}
// Sanitize and assume properties for each of the keywords
terms := [] *qTerm{}
for _, val := range strings.Split( line, " " ) {
if val == "" {
continue
}
term := qTerm{
Org: val,
Value: strings.ToUpper( strings.Trim( val, " " ) ),
ProblyRoute: strings.ContainsAny( val, utils.ROUTE_CHARS ),
}
terms = append( terms, &term )
}
// Search for route name first, otherwise search in other props
for _, entry := range *busStops {
// Search for RouteId
for _, term := range terms {
if term.ProblyRoute && term.Value == entry.RouteId {
if route != "" && route != term.Value {
return nil, fmt.Errorf( "Cannot %s & %s", route, term.Value )
}
matches = append( matches, entry )
route = entry.RouteId
break
}
if test( entry, term.Value ) {
matches = append( matches, entry )
break
}
}
}
searchTerms := [] *qTerm{}
matches_in := []BusStop{}
for _, term := range terms {
if term.ProblyRoute {
continue
}
searchTerms = append( searchTerms, term )
}
if 0 < len( searchTerms ) {
// If route found, filter out all other route
// then search the terms within that route
for _, entry := range matches {
if route != "" && entry.RouteId != route {
continue
}
for _, term := range searchTerms {
if test( entry, term.Value ) {
matches_in = append( matches_in, entry )
break
}
}
}
matches = matches_in
return &QueryObject{
Route: route,
BusStops: &matches,
SearchTerms: &searchTerms,
}, err
} else if route != "" {
// Route listing
st := map[string] *BusStop{}
for _, entry := range *busStops {
if entry.RouteId != route {
continue
}
if _, ok := st[ entry.Direction ]; ok {
continue
}
st[ entry.Direction ] = &entry
for st[ entry.Direction ].PrevStop() != nil {
st[ entry.Direction ] = st[ entry.Direction ].PrevStop()
}
}
return &QueryObject{ Route: route, RouteStarts: &st, BusStops: nil }, nil
}
return nil, fmt.Errorf( "Cannot parse: %s", line )
qrReturn:
var iqr query.IQueryResult
iqr = &qr
return iqr
}

View File

@ -2,11 +2,32 @@ package bus
import (
"fmt"
"strings"
"testing"
)
func TestQuerySchedule( t *testing.T ) {
qo := Query( "zh", "水" )
func TestQuery( t *testing.T ) {
qo := Query( "zh-Hant", "K73" )
mesg, err := qo.Message()
if err != nil {
t.Errorf( "Unexpected Error: %s", err )
}
fmt.Print( qo.Message() )
if !strings.Contains( mesg, "K73\\-O" ) {
t.Errorf( "Expected Route Listing, got \"%s\" instead", mesg )
}
qo = Query( "zh-Hant", "K76 池" )
mesg, err = qo.Message()
if err == nil {
t.Errorf( "Expecting error, got \"%s\" instead", mesg )
}
qo = Query( "zh-Hant", "K73 池" )
mesg, err = qo.Message()
if err != nil {
t.Errorf( "Unexpected Error: %s", err )
}
fmt.Println( mesg )
}

10
i18n/Generics.go Normal file
View File

@ -0,0 +1,10 @@
package i18n
import (
query "github.com/tgckpg/golifehk/query"
)
type Generics struct {
Name *map[string] string
query.Searchable
}

55
main.go
View File

@ -1,9 +1,11 @@
package main
import (
"fmt"
"log"
"os"
mtrbus "github.com/tgckpg/golifehk/datasources/mtr/bus"
kmb "github.com/tgckpg/golifehk/datasources/kmb"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
@ -29,13 +31,58 @@ func main() {
log.Printf( "[%s] %s", update.Message.From.UserName, update.Message.Text )
result := mtrbus.Query( "zh", update.Message.Text )
mesg, err := mtrbus.Query( "zh-Hant", update.Message.Text ).Message()
if update.Message.Chat.IsGroup() {
if err == nil {
msg := tgbotapi.NewMessage( update.Message.Chat.ID, mesg )
msg.ParseMode = "MarkdownV2"
msg.ReplyToMessageID = update.Message.MessageID
bot.Send( msg )
}
} else {
var msg tgbotapi.MessageConfig
if err == nil {
msg = tgbotapi.NewMessage( update.Message.Chat.ID, mesg )
msg.ParseMode = "MarkdownV2"
} else {
msg = tgbotapi.NewMessage( update.Message.Chat.ID, fmt.Sprintf( "%s", err ) )
}
if result.Error == nil || !update.Message.Chat.IsGroup() {
msg := tgbotapi.NewMessage( update.Message.Chat.ID, result.Message() )
msg.ParseMode = "MarkdownV2"
msg.ReplyToMessageID = update.Message.MessageID
bot.Send( msg )
}
mesg, err2 := kmb.Query( "zh-Hant", update.Message.Text ).Message()
if err != nil && err2 != nil {
continue
}
err = err2
if update.Message.Chat.IsGroup() {
if err == nil {
msg := tgbotapi.NewMessage( update.Message.Chat.ID, mesg )
msg.ParseMode = "MarkdownV2"
msg.ReplyToMessageID = update.Message.MessageID
bot.Send( msg )
}
} else {
var msg tgbotapi.MessageConfig
if err == nil {
msg = tgbotapi.NewMessage( update.Message.Chat.ID, mesg )
msg.ParseMode = "MarkdownV2"
} else {
msg = tgbotapi.NewMessage( update.Message.Chat.ID, fmt.Sprintf( "%s", err ) )
}
msg.ReplyToMessageID = update.Message.MessageID
bot.Send( msg )
}
}

5
query/IQueryResult.go Normal file
View File

@ -0,0 +1,5 @@
package query
type IQueryResult interface {
Message() ( string, error )
}

40
query/Searchable.go Normal file
View File

@ -0,0 +1,40 @@
package query
import (
"strings"
)
type ISearchable interface {
Test( string ) bool
GetKey() *string
}
type Searchable struct {
Key *string
SearchData *[] *string
}
func ( this *Searchable ) Test( val string ) bool {
data := this.SearchData
if data == nil {
return false
}
for _, v := range *data {
if strings.Contains( *v, val ) {
return true
}
}
return false
}
func ( this *Searchable ) GetKey() *string {
return this.Key
}
type ByKey [] ISearchable
func (a ByKey) Len() int { return len(a) }
func (a ByKey) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByKey) Less(i, j int) bool { return *a[i].GetKey() < *a[j].GetKey() }

74
query/query.go Normal file
View File

@ -0,0 +1,74 @@
package query
import (
"fmt"
"strings"
)
type QTerm struct {
Org string
Value string
IsKey bool
}
type QueryObject struct {
Key string
SearchTerms *[] *QTerm
Results *[] ISearchable
}
func Parse( line string, entries *[] ISearchable ) ( *QueryObject, error ) {
var Key string = ""
// Sanitize and assume properties for each of the keywords
terms := [] *QTerm{}
for _, val := range strings.Split( line, " " ) {
if val == "" {
continue
}
term := QTerm{
Org: val,
Value: strings.Trim( val, " " ),
}
terms = append( terms, &term )
}
lookForKey:
for _, term := range terms {
for _, entry := range *entries {
if term.Value == *entry.GetKey() {
Key = term.Value
term.IsKey = true
break lookForKey
}
}
}
matches := [] ISearchable{}
if Key != "" && len( terms ) == 1 {
for _, entry := range *entries {
if Key == *entry.GetKey() {
matches = append( matches, entry )
}
}
return &QueryObject{ Key: Key, Results: &matches, SearchTerms: &terms }, nil
} else if 0 < len( terms ) {
for _, entry := range *entries {
for _, term := range terms {
if term.IsKey {
continue
}
if ( Key == "" || Key == *entry.GetKey() ) && entry.Test( term.Value ) {
matches = append( matches, entry )
}
}
}
return &QueryObject{ Key: Key, Results: &matches, SearchTerms: &terms }, nil
}
return nil, fmt.Errorf( "Cannot parse: %s", line )
}

79
query/query_test.go Normal file
View File

@ -0,0 +1,79 @@
package query
import (
"testing"
)
type TestItem struct {
Searchable
}
func TestQuery( t *testing.T ) {
testItems := [] ISearchable{}
s1234 := "1234"
sApple := "Apple"
s0000 := "0000"
sDat0 := "Dat0"
data0 := [] *string{}
data0 = append( data0, &s1234 )
data0 = append( data0, &sApple )
t0 := TestItem{}
t0.Key = &sDat0
t0.SearchData = &data0
testItems = append( testItems, &t0 )
sDat1 := "Dat1"
data1 := [] *string{}
data1 = append( data1, &s0000 )
data1 = append( data1, &sApple )
t1 := TestItem{}
t1.Key = &sDat1
t1.SearchData = &data1
testItems = append( testItems, &t1 )
sDat2 := "Dat2"
data2 := [] *string{}
data2 = append( data2, &sDat0 )
t2 := TestItem{}
t2.Key = &sDat2
t2.SearchData = &data2
testItems = append( testItems, &t2 )
qo, err := Parse( "Apple", &testItems )
if err != nil {
t.Error( err )
}
if len(*qo.Results) != 2 {
t.Error( "Expected 2 results when searching for \"Apple\"" )
}
qo, err = Parse( "0000", &testItems )
if err != nil {
t.Error( err )
}
if len(*qo.Results) != 1 {
t.Error( "Expected 1 results when searching for \"0000\"" )
}
qo, err = Parse( "Dat0", &testItems )
if err != nil {
t.Error( err )
}
if len(*qo.Results) != 1 {
t.Error( "Expected 1 results when searching for \"Dat0\"" )
}
qo, err = Parse( "Dat2 Dat0", &testItems )
if err != nil {
t.Error( err )
}
if len(*qo.Results) != 1 {
t.Error( "Expected 1 results when searching for \"Dat2 Dat0\"" )
}
}

View File

@ -9,4 +9,4 @@ var WORKDIR string = TryGetEnv( "GOLIFEHK_WORKDIR", filepath.Join( os.TempDir(),
var BOM string = bytes.NewBuffer([]byte{ 0xEF, 0xBB, 0xBF }).String()
const ROUTE_CHARS string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-"
const KEY_CHARS string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-"

View File

@ -3,8 +3,19 @@ package utils
import (
"os"
"strconv"
"strings"
)
var MARKDOWN_ESC []string = []string{"_", "*", "[", "]", "(", ")", "~", "`", ">", "#", "+", "-", "=", "|", "{", "}", ".", "!"}
func WriteMDv2Text( sb *strings.Builder, t string ) {
for _, c := range MARKDOWN_ESC {
t = strings.Replace( t, c, "\\" + c, -1 )
}
sb.WriteString( t )
}
func TryGetEnv[T any]( name string, fallback T ) T {
v := os.Getenv( name )