Refactoring codes for more tg message types
This commit is contained in:
56
adaptors/tg/botsend.go
Normal file
56
adaptors/tg/botsend.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package tg
|
||||||
|
|
||||||
|
import (
|
||||||
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
|
query "github.com/tgckpg/golifehk/query"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BotSendText(bot *tgbotapi.BotAPI, update *tgbotapi.Update, mesg *string) {
|
||||||
|
var msg tgbotapi.MessageConfig
|
||||||
|
msg = tgbotapi.NewMessage(update.Message.Chat.ID, *mesg)
|
||||||
|
msg.ParseMode = "MarkdownV2"
|
||||||
|
|
||||||
|
msg.ReplyToMessageID = update.Message.MessageID
|
||||||
|
bot.Send(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BotSend(bot *tgbotapi.BotAPI, update *tgbotapi.Update, qResult query.IQueryResult) (bool, error) {
|
||||||
|
|
||||||
|
var msg tgbotapi.MessageConfig
|
||||||
|
mesg, err := qResult.Message()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// not sent, error
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mesgType := qResult.DataType()
|
||||||
|
|
||||||
|
switch mesgType {
|
||||||
|
case "IGNORE":
|
||||||
|
// not sent with no error tells the parent to look for other processors
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = tgbotapi.NewMessage(update.Message.Chat.ID, mesg)
|
||||||
|
msg.ParseMode = "MarkdownV2"
|
||||||
|
|
||||||
|
switch mesgType {
|
||||||
|
case "PlainText":
|
||||||
|
case "Table":
|
||||||
|
|
||||||
|
button := tgbotapi.NewInlineKeyboardButtonData(
|
||||||
|
"Show Status", // what user sees
|
||||||
|
"status_cmd", // what bot receives
|
||||||
|
)
|
||||||
|
|
||||||
|
msg.ReplyMarkup = tgbotapi.NewInlineKeyboardMarkup(
|
||||||
|
tgbotapi.NewInlineKeyboardRow(button, button),
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.ReplyToMessageID = update.Message.MessageID
|
||||||
|
bot.Send(msg)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
@@ -12,7 +12,8 @@ type CChar struct {
|
|||||||
JyutPing *CJyutPing
|
JyutPing *CJyutPing
|
||||||
DukJam *[]*DukJam
|
DukJam *[]*DukJam
|
||||||
_JiDukJam *[]*CJyutPing
|
_JiDukJam *[]*CJyutPing
|
||||||
query.Searchable
|
query.Words
|
||||||
|
query.NoGeoLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
type DukJam struct {
|
type DukJam struct {
|
||||||
@@ -25,7 +26,8 @@ type CJyutPing struct {
|
|||||||
SearchKey string // Searchable key
|
SearchKey string // Searchable key
|
||||||
TungJamZi *[]*CChar
|
TungJamZi *[]*CChar
|
||||||
tSorted bool
|
tSorted bool
|
||||||
query.Searchable
|
query.Words
|
||||||
|
query.NoGeoLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *CJyutPing) Test(val string) bool {
|
func (this *CJyutPing) Test(val string) bool {
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ type QueryResult struct {
|
|||||||
Lang string
|
Lang string
|
||||||
Error error
|
Error error
|
||||||
Query *query.QueryObject
|
Query *query.QueryObject
|
||||||
|
|
||||||
|
ResultType string
|
||||||
|
isConsumed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeCCharInfo(sb *strings.Builder, cc *CChar) {
|
func writeCCharInfo(sb *strings.Builder, cc *CChar) {
|
||||||
@@ -37,14 +40,20 @@ func writeCCharInfo(sb *strings.Builder, cc *CChar) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this QueryResult) DataType() string { return this.ResultType }
|
||||||
|
func (this QueryResult) Consumed() bool { return this.isConsumed }
|
||||||
|
func (this QueryResult) GetTableData() [][]map[string]string { return nil }
|
||||||
|
|
||||||
func (this QueryResult) Message() (string, error) {
|
func (this QueryResult) Message() (string, error) {
|
||||||
|
|
||||||
|
this.ResultType = "PlainText"
|
||||||
|
|
||||||
if this.Error != nil {
|
if this.Error != nil {
|
||||||
return "", this.Error
|
return "", this.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
if this.Query == nil {
|
if this.Query == nil {
|
||||||
panic("Query is nil")
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
sb := strings.Builder{}
|
sb := strings.Builder{}
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
package cjlookup
|
package cjlookup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/tgckpg/golifehk/query"
|
"github.com/tgckpg/golifehk/query"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Query(lang string, message string) query.IQueryResult {
|
func Query(q query.QueryMessage) query.IQueryResult {
|
||||||
|
|
||||||
|
lang := q.Lang
|
||||||
|
message := q.Text
|
||||||
|
|
||||||
var qResults *query.QueryObject
|
var qResults *query.QueryObject
|
||||||
var err error
|
var err error
|
||||||
var searchables *[]query.ISearchable
|
var searchables *[]query.ISearchable
|
||||||
@@ -17,9 +20,12 @@ func Query(lang string, message string) query.IQueryResult {
|
|||||||
|
|
||||||
// Only look up jyut ping
|
// Only look up jyut ping
|
||||||
if !strings.HasPrefix(messageU, "JP ") {
|
if !strings.HasPrefix(messageU, "JP ") {
|
||||||
err = fmt.Errorf("Invalid query")
|
qr.ResultType = "IGNORE"
|
||||||
|
goto qrReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qr.isConsumed = true
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
qr.Error = err
|
qr.Error = err
|
||||||
goto qrReturn
|
goto qrReturn
|
||||||
@@ -28,7 +34,7 @@ func Query(lang string, message string) query.IQueryResult {
|
|||||||
messageU = messageU[3:]
|
messageU = messageU[3:]
|
||||||
searchables, err = getSearchables()
|
searchables, err = getSearchables()
|
||||||
|
|
||||||
qResults, err = query.Parse(messageU, searchables)
|
qResults, err = query.MatchKeys(messageU, searchables)
|
||||||
|
|
||||||
qr.Query = qResults
|
qr.Query = qResults
|
||||||
|
|
||||||
|
|||||||
@@ -1,176 +1,181 @@
|
|||||||
package kmb
|
package kmb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
query "github.com/tgckpg/golifehk/query"
|
query "github.com/tgckpg/golifehk/query"
|
||||||
utils "github.com/tgckpg/golifehk/utils"
|
utils "github.com/tgckpg/golifehk/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type QueryResult struct {
|
type QueryResult struct {
|
||||||
Schedules *map[*RouteStop] *[] *Schedule
|
Schedules *map[*RouteStop]*[]*Schedule
|
||||||
Lang string
|
Lang string
|
||||||
Error error
|
Error error
|
||||||
Query *query.QueryObject
|
Query *query.QueryObject
|
||||||
|
|
||||||
|
isConsumed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeRouteHead( sb *strings.Builder, r *RouteStop ) {
|
func writeRouteHead(sb *strings.Builder, r *RouteStop) {
|
||||||
utils.WriteMDv2Text( sb, r.RouteId )
|
utils.WriteMDv2Text(sb, r.RouteId)
|
||||||
if r.Direction == "O" {
|
if r.Direction == "O" {
|
||||||
sb.WriteString( "↑" )
|
sb.WriteString("↑")
|
||||||
} else if r.Direction == "I" {
|
} else if r.Direction == "I" {
|
||||||
sb.WriteString( "↓" )
|
sb.WriteString("↓")
|
||||||
}
|
}
|
||||||
if r.ServiceType != "1" {
|
if r.ServiceType != "1" {
|
||||||
utils.WriteMDv2Text( sb, utils.ToPower( r.ServiceType ) )
|
utils.WriteMDv2Text(sb, utils.ToPower(r.ServiceType))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeShortRoute( lang *string, sb *strings.Builder, r *RouteStop ) {
|
func writeShortRoute(lang *string, sb *strings.Builder, r *RouteStop) {
|
||||||
|
|
||||||
if r.PrevStop() != nil {
|
if r.PrevStop() != nil {
|
||||||
utils.WriteMDv2Text( sb, (*(r.PrevStop().BusStop).Name)[ *lang ] )
|
utils.WriteMDv2Text(sb, (*(r.PrevStop().BusStop).Name)[*lang])
|
||||||
sb.WriteString( " \\> " )
|
sb.WriteString(" \\> ")
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.WriteString( "*" )
|
sb.WriteString("*")
|
||||||
utils.WriteMDv2Text( sb, (*(r.BusStop).Name)[ *lang ] )
|
utils.WriteMDv2Text(sb, (*(r.BusStop).Name)[*lang])
|
||||||
sb.WriteString( "*" )
|
sb.WriteString("*")
|
||||||
|
|
||||||
if r.NextStop() != nil {
|
if r.NextStop() != nil {
|
||||||
sb.WriteString( " \\> " )
|
sb.WriteString(" \\> ")
|
||||||
utils.WriteMDv2Text( sb, (*(r.NextStop().BusStop).Name)[ *lang ] )
|
utils.WriteMDv2Text(sb, (*(r.NextStop().BusStop).Name)[*lang])
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.WriteString( "\n" )
|
sb.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func ( this *QueryResult ) Message() ( string, error ) {
|
func (this QueryResult) DataType() string { return "MarkdownV2" }
|
||||||
|
func (this QueryResult) Consumed() bool { return this.isConsumed }
|
||||||
|
func (this QueryResult) GetTableData() [][]map[string]string { return nil }
|
||||||
|
|
||||||
if this.Error != nil {
|
func (this *QueryResult) Message() (string, error) {
|
||||||
return "", this.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
sb := strings.Builder{}
|
if this.Error != nil {
|
||||||
|
return "", this.Error
|
||||||
|
}
|
||||||
|
|
||||||
if 0 < len( *this.Query.Results ) {
|
sb := strings.Builder{}
|
||||||
|
|
||||||
// Print Stop Names, then print the list of routes
|
if 0 < len(*this.Query.Results) {
|
||||||
if this.Query.Key == "" {
|
|
||||||
busStops := map[string] *BusStop{}
|
|
||||||
for _, item := range *this.Query.Results {
|
|
||||||
var r *RouteStop
|
|
||||||
r = any( item ).( *RouteStop )
|
|
||||||
|
|
||||||
b := r.BusStop
|
// Print Stop Names, then print the list of routes
|
||||||
if b.Routes == nil {
|
if this.Query.Key == "" {
|
||||||
continue
|
busStops := map[string]*BusStop{}
|
||||||
}
|
for _, item := range *this.Query.Results {
|
||||||
|
var r *RouteStop
|
||||||
|
r = any(item).(*RouteStop)
|
||||||
|
|
||||||
busStops[ b.BusStopId ] = b
|
b := r.BusStop
|
||||||
}
|
if b.Routes == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
for _, b := range busStops {
|
busStops[b.BusStopId] = b
|
||||||
utils.WriteMDv2Text( &sb, (*b.Name)[ this.Lang ] )
|
}
|
||||||
sb.WriteString( "\n " )
|
|
||||||
for _, route := range *b.Routes {
|
|
||||||
writeRouteHead( &sb, route )
|
|
||||||
sb.WriteString( " " )
|
|
||||||
}
|
|
||||||
sb.WriteString( "\n" )
|
|
||||||
}
|
|
||||||
|
|
||||||
// We got a route key
|
for _, b := range busStops {
|
||||||
} else {
|
utils.WriteMDv2Text(&sb, (*b.Name)[this.Lang])
|
||||||
|
sb.WriteString("\n ")
|
||||||
|
for _, route := range *b.Routes {
|
||||||
|
writeRouteHead(&sb, route)
|
||||||
|
sb.WriteString(" ")
|
||||||
|
}
|
||||||
|
sb.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
// We also got other search keys with 1 < Results
|
// We got a route key
|
||||||
// Get the ETA for this stop
|
} else {
|
||||||
if 1 < len( *this.Query.SearchTerms ) {
|
|
||||||
|
|
||||||
now := time.Now()
|
// We also got other search keys with 1 < Results
|
||||||
for _, item := range *this.Query.Results {
|
// Get the ETA for this stop
|
||||||
var r *RouteStop
|
if 1 < len(*this.Query.SearchTerms) {
|
||||||
r = any( item ).( *RouteStop )
|
|
||||||
writeRouteHead( &sb, r )
|
|
||||||
sb.WriteString( "\n" )
|
|
||||||
writeShortRoute( &this.Lang, &sb, r )
|
|
||||||
|
|
||||||
for _, schedule := range *(*this.Schedules)[ r ] {
|
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)
|
||||||
|
|
||||||
if !schedule.ETA.IsZero() {
|
for _, schedule := range *(*this.Schedules)[r] {
|
||||||
|
|
||||||
_m := schedule.ETA.Sub( now ).Minutes()
|
if !schedule.ETA.IsZero() {
|
||||||
|
|
||||||
sb.WriteString( " \\* " )
|
_m := schedule.ETA.Sub(now).Minutes()
|
||||||
txt := "%.0f min(s)"
|
|
||||||
|
|
||||||
if this.Lang == "zh-Hant" {
|
sb.WriteString(" \\* ")
|
||||||
txt = "%.0f 分鐘"
|
txt := "%.0f min(s)"
|
||||||
}
|
|
||||||
|
|
||||||
utils.WriteMDv2Text( &sb, fmt.Sprintf( txt, _m ) )
|
if this.Lang == "zh-Hant" {
|
||||||
|
txt = "%.0f 分鐘"
|
||||||
|
}
|
||||||
|
|
||||||
if _m < 0 {
|
utils.WriteMDv2Text(&sb, fmt.Sprintf(txt, _m))
|
||||||
sb.WriteString( " 走左了?" )
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if schedule.Remarks_en != "" {
|
if _m < 0 {
|
||||||
sb.WriteString( " \\*\\* " )
|
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" )
|
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
|
sb.WriteString("\n")
|
||||||
} else {
|
}
|
||||||
// Result contains all route stops, we only need the starting one
|
|
||||||
routes := [] *RouteStop{}
|
|
||||||
|
|
||||||
for _, item := range *this.Query.Results {
|
// We got only the route key, proceed to list the route stops
|
||||||
var r *RouteStop
|
} else {
|
||||||
r = any( item ).( *RouteStop )
|
// Result contains all route stops, we only need the starting one
|
||||||
if r.PrevStop() == nil {
|
routes := []*RouteStop{}
|
||||||
routes = append( routes, r )
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort( ByRoute( routes ) )
|
for _, item := range *this.Query.Results {
|
||||||
|
var r *RouteStop
|
||||||
|
r = any(item).(*RouteStop)
|
||||||
|
if r.PrevStop() == nil {
|
||||||
|
routes = append(routes, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, r := range routes {
|
sort.Sort(ByRoute(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( " \\> " )
|
for _, r := range routes {
|
||||||
}
|
writeRouteHead(&sb, r)
|
||||||
sb.WriteString( "\n" )
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return "", fmt.Errorf( "No Results" )
|
return "", fmt.Errorf("No Results")
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.String(), nil
|
return sb.String(), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,65 +1,66 @@
|
|||||||
package kmb
|
package kmb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
query "github.com/tgckpg/golifehk/query"
|
query "github.com/tgckpg/golifehk/query"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RouteStop struct {
|
type RouteStop struct {
|
||||||
BusStop *BusStop
|
BusStop *BusStop
|
||||||
RouteId string `json:"route"`
|
RouteId string `json:"route"`
|
||||||
ServiceType string `json:"service_type"`
|
ServiceType string `json:"service_type"`
|
||||||
Direction string `json:"bound"`
|
Direction string `json:"bound"`
|
||||||
StationSeq int `json:"seq,string"`
|
StationSeq int `json:"seq,string"`
|
||||||
StationId string `json:"stop"`
|
StationId string `json:"stop"`
|
||||||
|
|
||||||
RouteStops *map[int] *RouteStop
|
RouteStops *map[int]*RouteStop
|
||||||
query.Searchable
|
query.Words
|
||||||
|
query.NoGeoLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
type RouteStops struct {
|
type RouteStops struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
DateCreated string `json:"generated_timestamp"`
|
DateCreated string `json:"generated_timestamp"`
|
||||||
RouteStops [] *RouteStop `json:"data"`
|
RouteStops []*RouteStop `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func ( routeStop RouteStop ) PrevStop() *RouteStop {
|
func (routeStop RouteStop) PrevStop() *RouteStop {
|
||||||
if v, hasKey := (*routeStop.RouteStops)[ routeStop.StationSeq - 1 ]; hasKey {
|
if v, hasKey := (*routeStop.RouteStops)[routeStop.StationSeq-1]; hasKey {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ( routeStop RouteStop ) NextStop() *RouteStop {
|
func (routeStop RouteStop) NextStop() *RouteStop {
|
||||||
if v, hasKey := (*routeStop.RouteStops)[ routeStop.StationSeq + 1 ]; hasKey {
|
if v, hasKey := (*routeStop.RouteStops)[routeStop.StationSeq+1]; hasKey {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ( this *RouteStop ) Reload() {
|
func (this *RouteStop) Reload() {
|
||||||
|
|
||||||
searchData := [] *string{}
|
searchData := []*string{}
|
||||||
busStop := *this.BusStop
|
busStop := *this.BusStop
|
||||||
searchData = append( searchData, &busStop.Name_en )
|
searchData = append(searchData, &busStop.Name_en)
|
||||||
searchData = append( searchData, &busStop.Name_tc )
|
searchData = append(searchData, &busStop.Name_tc)
|
||||||
|
|
||||||
this.Key = &this.RouteId
|
this.Key = &this.RouteId
|
||||||
this.SearchData = &searchData
|
this.SearchData = &searchData
|
||||||
}
|
}
|
||||||
|
|
||||||
type ByRoute [] *RouteStop
|
type ByRoute []*RouteStop
|
||||||
|
|
||||||
func (a ByRoute) Len() int { return len(a) }
|
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) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
func (a ByRoute) Less(i, j int) bool {
|
func (a ByRoute) Less(i, j int) bool {
|
||||||
_a := *a[i]
|
_a := *a[i]
|
||||||
_b := *a[j]
|
_b := *a[j]
|
||||||
if _a.RouteId == _b.RouteId {
|
if _a.RouteId == _b.RouteId {
|
||||||
if _a.Direction == _b.Direction {
|
if _a.Direction == _b.Direction {
|
||||||
return _a.ServiceType < _b.ServiceType
|
return _a.ServiceType < _b.ServiceType
|
||||||
}
|
}
|
||||||
return _a.Direction < _b.Direction
|
return _a.Direction < _b.Direction
|
||||||
}
|
}
|
||||||
return _a.RouteId < _b.RouteId
|
return _a.RouteId < _b.RouteId
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Query(lang string, message string) query.IQueryResult {
|
func Query(q query.QueryMessage) query.IQueryResult {
|
||||||
|
|
||||||
|
lang := q.Lang
|
||||||
|
message := q.Text
|
||||||
|
|
||||||
var qo *query.QueryObject
|
var qo *query.QueryObject
|
||||||
var err error
|
var err error
|
||||||
@@ -18,7 +21,7 @@ func Query(lang string, message string) query.IQueryResult {
|
|||||||
goto qrReturn
|
goto qrReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
qo, err = query.Parse(strings.ToUpper(message), routeStops)
|
qo, err = query.MatchKeys(strings.ToUpper(message), routeStops)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
qr.Error = err
|
qr.Error = err
|
||||||
goto qrReturn
|
goto qrReturn
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package bus
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
i18n "github.com/tgckpg/golifehk/i18n"
|
i18n "github.com/tgckpg/golifehk/i18n"
|
||||||
|
query "github.com/tgckpg/golifehk/query"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BusStop struct {
|
type BusStop struct {
|
||||||
@@ -10,8 +11,6 @@ type BusStop struct {
|
|||||||
Direction string
|
Direction string
|
||||||
StationSeq int
|
StationSeq int
|
||||||
StationId string
|
StationId string
|
||||||
Latitude float64
|
|
||||||
Longtitude float64
|
|
||||||
Name_zh string
|
Name_zh string
|
||||||
Name_en string
|
Name_en string
|
||||||
|
|
||||||
@@ -22,6 +21,7 @@ type BusStop struct {
|
|||||||
AltRoutes *map[string]*BusStop
|
AltRoutes *map[string]*BusStop
|
||||||
|
|
||||||
i18n.Generics
|
i18n.Generics
|
||||||
|
query.GeoLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *BusStop) PrevStop() *BusStop {
|
func (this *BusStop) PrevStop() *BusStop {
|
||||||
@@ -52,6 +52,14 @@ func (this *BusStop) Reload() {
|
|||||||
this.SearchData = &searchData
|
this.SearchData = &searchData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this BusStop) Register(registers map[string]struct{}) bool {
|
||||||
|
if _, ok := registers[this.StationId]; ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
registers[this.StationId] = struct{}{}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
type ByRoute []*BusStop
|
type ByRoute []*BusStop
|
||||||
|
|
||||||
func (a ByRoute) Len() int { return len(a) }
|
func (a ByRoute) Len() int { return len(a) }
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ type QueryResult struct {
|
|||||||
Schedules *map[*BusStop]*BusStopBuses
|
Schedules *map[*BusStop]*BusStopBuses
|
||||||
Lang string
|
Lang string
|
||||||
Error error
|
Error error
|
||||||
|
Source *query.QueryMessage
|
||||||
Query *query.QueryObject
|
Query *query.QueryObject
|
||||||
|
|
||||||
|
isConsumed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeShortRoute(lang *string, sb *strings.Builder, b *BusStop) {
|
func writeShortRoute(lang *string, sb *strings.Builder, b *BusStop) {
|
||||||
@@ -35,6 +38,10 @@ func writeShortRoute(lang *string, sb *strings.Builder, b *BusStop) {
|
|||||||
sb.WriteString("\n")
|
sb.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this QueryResult) DataType() string { return "MarkdownV2" }
|
||||||
|
func (this QueryResult) Consumed() bool { return this.isConsumed }
|
||||||
|
func (this QueryResult) GetTableData() [][]map[string]string { return nil }
|
||||||
|
|
||||||
func (this QueryResult) Message() (string, error) {
|
func (this QueryResult) Message() (string, error) {
|
||||||
|
|
||||||
if this.Error != nil {
|
if this.Error != nil {
|
||||||
@@ -56,12 +63,41 @@ func (this QueryResult) Message() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if q.Key == "" {
|
if q.Key == "" {
|
||||||
sort.Sort(query.ByKey(*q.Results))
|
|
||||||
for _, entry := range *q.Results {
|
loc := q.Message.Location
|
||||||
busStop := any(entry).(*BusStop)
|
if loc != nil {
|
||||||
utils.WriteMDv2Text(&sb, busStop.RouteId)
|
|
||||||
sb.WriteString(" ")
|
// Print nearest bus stops
|
||||||
writeShortRoute(&this.Lang, &sb, busStop)
|
for _, entry := range *q.Results {
|
||||||
|
busStop := any(entry).(*BusStop)
|
||||||
|
|
||||||
|
utils.WriteMDv2Text(&sb, fmt.Sprintf("%.2fm", busStop.Dist(loc.Lat(), loc.Lon())))
|
||||||
|
sb.WriteString(" ")
|
||||||
|
sb.WriteString(" [")
|
||||||
|
utils.WriteMDv2Text(&sb, busStop.RouteId)
|
||||||
|
d := busStop.Direction
|
||||||
|
if d == "O" {
|
||||||
|
sb.WriteString("↑")
|
||||||
|
} else if d == "I" {
|
||||||
|
sb.WriteString("↓")
|
||||||
|
} else {
|
||||||
|
sb.WriteString("\\?")
|
||||||
|
}
|
||||||
|
utils.WriteMDv2Text(&sb, (*busStop.Name)[this.Lang])
|
||||||
|
utils.WriteMDv2Text(&sb, busStop.RouteId)
|
||||||
|
utils.WriteMDv2Text(&sb, (*busStop.Name)[this.Lang])
|
||||||
|
sb.WriteString(")")
|
||||||
|
sb.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
sort.Sort(query.ByKey(*q.Results))
|
||||||
|
for _, entry := range *q.Results {
|
||||||
|
busStop := any(entry).(*BusStop)
|
||||||
|
utils.WriteMDv2Text(&sb, busStop.RouteId)
|
||||||
|
sb.WriteString(" ")
|
||||||
|
writeShortRoute(&this.Lang, &sb, busStop)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if 1 == len(*q.SearchTerms) {
|
} else if 1 == len(*q.SearchTerms) {
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ func readBusStopData(r io.Reader) (*map[string]*BusStop, error) {
|
|||||||
entry.Latitude = v
|
entry.Latitude = v
|
||||||
case "STATION_LONGITUDE":
|
case "STATION_LONGITUDE":
|
||||||
v, _ := strconv.ParseFloat(value, 64)
|
v, _ := strconv.ParseFloat(value, 64)
|
||||||
entry.Longtitude = v
|
entry.Longitude = v
|
||||||
case "STATION_NAME_CHI":
|
case "STATION_NAME_CHI":
|
||||||
entry.Name_zh = value
|
entry.Name_zh = value
|
||||||
case "STATION_NAME_ENG":
|
case "STATION_NAME_ENG":
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import (
|
|||||||
query "github.com/tgckpg/golifehk/query"
|
query "github.com/tgckpg/golifehk/query"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Query(lang string, message string) query.IQueryResult {
|
func Query(q query.QueryMessage) query.IQueryResult {
|
||||||
|
|
||||||
|
lang := q.Lang
|
||||||
|
|
||||||
var qBusStops *query.QueryObject
|
var qBusStops *query.QueryObject
|
||||||
var err error
|
var err error
|
||||||
@@ -18,7 +20,14 @@ func Query(lang string, message string) query.IQueryResult {
|
|||||||
goto qrReturn
|
goto qrReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
qBusStops, err = query.Parse(strings.ToUpper(message), busStops)
|
if q.Text != "" {
|
||||||
|
qBusStops, err = query.MatchKeys(strings.ToUpper(q.Text), busStops)
|
||||||
|
} else if q.Location != nil {
|
||||||
|
qBusStops, err = query.MatchNearest(*q.Location, busStops, 100, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
qBusStops.Message = &q
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
qr.Error = err
|
qr.Error = err
|
||||||
goto qrReturn
|
goto qrReturn
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -7,3 +7,5 @@ require (
|
|||||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa
|
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa
|
||||||
golang.org/x/text v0.34.0
|
golang.org/x/text v0.34.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require github.com/tidwall/geodesic v1.52.4 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -1,5 +1,7 @@
|
|||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
||||||
|
github.com/tidwall/geodesic v1.52.4 h1:nT9cvYziVbmqFMDuvJzCJKvBJ9wFx0gRwvVrt86fpXg=
|
||||||
|
github.com/tidwall/geodesic v1.52.4/go.mod h1:SNL5vSG4X+o0ExTya69PX7/ZQ2SAvmjAxI+o5ZGJsxs=
|
||||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
|
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
|
||||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
|
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
|
||||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package i18n
|
package i18n
|
||||||
|
|
||||||
import (
|
import (
|
||||||
query "github.com/tgckpg/golifehk/query"
|
query "github.com/tgckpg/golifehk/query"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Generics struct {
|
type Generics struct {
|
||||||
Name *map[string] string
|
Name *map[string]string
|
||||||
query.Searchable
|
query.Words
|
||||||
}
|
}
|
||||||
|
|||||||
42
main.go
42
main.go
@@ -6,6 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
|
tgadaptor "github.com/tgckpg/golifehk/adaptors/tg"
|
||||||
cjlookup "github.com/tgckpg/golifehk/datasources/cjlookup"
|
cjlookup "github.com/tgckpg/golifehk/datasources/cjlookup"
|
||||||
kmb "github.com/tgckpg/golifehk/datasources/kmb"
|
kmb "github.com/tgckpg/golifehk/datasources/kmb"
|
||||||
mtrbus "github.com/tgckpg/golifehk/datasources/mtr/bus"
|
mtrbus "github.com/tgckpg/golifehk/datasources/mtr/bus"
|
||||||
@@ -13,15 +14,6 @@ import (
|
|||||||
utils "github.com/tgckpg/golifehk/utils"
|
utils "github.com/tgckpg/golifehk/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func botsend(bot *tgbotapi.BotAPI, update *tgbotapi.Update, mesg *string) {
|
|
||||||
var msg tgbotapi.MessageConfig
|
|
||||||
msg = tgbotapi.NewMessage(update.Message.Chat.ID, *mesg)
|
|
||||||
msg.ParseMode = "MarkdownV2"
|
|
||||||
|
|
||||||
msg.ReplyToMessageID = update.Message.MessageID
|
|
||||||
bot.Send(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
bot, err := tgbotapi.NewBotAPI(os.Getenv("TELEGRAM_API_TOKEN"))
|
bot, err := tgbotapi.NewBotAPI(os.Getenv("TELEGRAM_API_TOKEN"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -48,12 +40,12 @@ func main() {
|
|||||||
mesg, processed := utils.SystemControl(update.Message)
|
mesg, processed := utils.SystemControl(update.Message)
|
||||||
if processed {
|
if processed {
|
||||||
if mesg != "" {
|
if mesg != "" {
|
||||||
botsend(bot, &update, &mesg)
|
tgadaptor.BotSendText(bot, &update, &mesg)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
f_queries := []func(string, string) query.IQueryResult{
|
f_queries := []func(query.QueryMessage) query.IQueryResult{
|
||||||
cjlookup.Query,
|
cjlookup.Query,
|
||||||
mtrbus.Query,
|
mtrbus.Query,
|
||||||
kmb.Query,
|
kmb.Query,
|
||||||
@@ -61,21 +53,35 @@ func main() {
|
|||||||
|
|
||||||
var f_sent bool = false
|
var f_sent bool = false
|
||||||
var f_err error = nil
|
var f_err error = nil
|
||||||
for _, Query := range f_queries {
|
|
||||||
var err error
|
|
||||||
mesg, err = Query("zh-Hant", update.Message.Text).Message()
|
|
||||||
|
|
||||||
if err == nil {
|
tgMesg := update.Message
|
||||||
|
q := query.QueryMessage{Lang: "zh-Hant", Text: tgMesg.Text}
|
||||||
|
|
||||||
|
if tgMesg.Location != nil {
|
||||||
|
q.Location = &query.GeoLocation{tgMesg.Location.Latitude, tgMesg.Location.Longitude}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, Query := range f_queries {
|
||||||
|
|
||||||
|
qResult := Query(q)
|
||||||
|
sent, err := tgadaptor.BotSend(bot, &update, qResult)
|
||||||
|
|
||||||
|
if sent {
|
||||||
f_sent = true
|
f_sent = true
|
||||||
botsend(bot, &update, &mesg)
|
}
|
||||||
} else if f_err == nil {
|
|
||||||
|
if err != nil {
|
||||||
f_err = err
|
f_err = err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if qResult.Consumed() {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isGroup && !f_sent && f_err != nil {
|
if !isGroup && !f_sent && f_err != nil {
|
||||||
mesg := utils.MDv2Text(fmt.Sprintf("%s", f_err))
|
mesg := utils.MDv2Text(fmt.Sprintf("%s", f_err))
|
||||||
botsend(bot, &update, &mesg)
|
tgadaptor.BotSendText(bot, &update, &mesg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
59
query/GeoLocation.go
Normal file
59
query/GeoLocation.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tidwall/geodesic"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GeoLocation struct {
|
||||||
|
Latitude float64
|
||||||
|
Longitude float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b GeoLocation) HasGeoLocation() bool { return true }
|
||||||
|
|
||||||
|
func (b GeoLocation) Lat() float64 { return b.Latitude }
|
||||||
|
func (b GeoLocation) Lon() float64 { return b.Longitude }
|
||||||
|
|
||||||
|
func (b GeoLocation) Dist(lat float64, lon float64) float64 {
|
||||||
|
var dist float64
|
||||||
|
geodesic.WGS84.Inverse(
|
||||||
|
lat, lon,
|
||||||
|
b.Latitude, b.Longitude,
|
||||||
|
&dist, nil, nil,
|
||||||
|
)
|
||||||
|
return dist
|
||||||
|
}
|
||||||
|
|
||||||
|
type NoGeoLocation struct{}
|
||||||
|
|
||||||
|
func (b NoGeoLocation) HasGeoLocation() bool { return false }
|
||||||
|
func (b NoGeoLocation) Lat() float64 { return 0 }
|
||||||
|
func (b NoGeoLocation) Lon() float64 { return 0 }
|
||||||
|
func (b NoGeoLocation) Dist(lat, lon float64) float64 { return 0 }
|
||||||
|
func (b NoGeoLocation) Register(map[string]struct{}) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type GeoLocations []ISearchable
|
||||||
|
|
||||||
|
func (m GeoLocations) SortByNearest(p GeoLocation) {
|
||||||
|
sort.Slice(m, func(i, j int) bool {
|
||||||
|
return m[i].Dist(p.Lat(), p.Lon()) < m[j].Dist(p.Lat(), p.Lon())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b GeoLocation) Register(map[string]struct{}) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m GeoLocations) Clean() *GeoLocations {
|
||||||
|
registers := map[string]struct{}{}
|
||||||
|
items := &GeoLocations{}
|
||||||
|
for _, item := range m {
|
||||||
|
if item.HasGeoLocation() && item.Register(registers) {
|
||||||
|
*items = append(*items, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package query
|
|
||||||
|
|
||||||
type IQueryResult interface {
|
|
||||||
Message() ( string, error )
|
|
||||||
}
|
|
||||||
@@ -1,40 +1,15 @@
|
|||||||
package query
|
package query
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ISearchable interface {
|
type ISearchable interface {
|
||||||
Test( string ) bool
|
HasWords() bool
|
||||||
GetKey() *string
|
Test(string) bool
|
||||||
|
GetKey() *string
|
||||||
|
|
||||||
|
HasGeoLocation() bool
|
||||||
|
Lat() float64
|
||||||
|
Lon() float64
|
||||||
|
Dist(lat float64, lon float64) float64
|
||||||
|
|
||||||
|
// Clean() filtering
|
||||||
|
Register(map[string]struct{}) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
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() }
|
|
||||||
|
|||||||
43
query/Words.go
Normal file
43
query/Words.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Words struct {
|
||||||
|
Key *string
|
||||||
|
SearchData *[]*string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Words) HasWords() bool { return true }
|
||||||
|
|
||||||
|
func (this *Words) 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 *Words) 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() }
|
||||||
|
|
||||||
|
type NoWords struct{}
|
||||||
|
|
||||||
|
func (this NoWords) HasWords() bool { return false }
|
||||||
|
func (this NoWords) Test(val string) bool { return false }
|
||||||
|
func (this NoWords) GetKey() *string { return nil }
|
||||||
33
query/geo_test.go
Normal file
33
query/geo_test.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type City struct {
|
||||||
|
Name string
|
||||||
|
NoWords
|
||||||
|
GeoLocation
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGeo(t *testing.T) {
|
||||||
|
|
||||||
|
B := GeoLocations{
|
||||||
|
City{Name: "Osaka", GeoLocation: GeoLocation{34.6937, 135.5023}},
|
||||||
|
City{Name: "Nagoya", GeoLocation: GeoLocation{35.1815, 136.9066}},
|
||||||
|
City{Name: "Sapporo", GeoLocation: GeoLocation{43.0618, 141.3545}},
|
||||||
|
City{Name: "Yokohama", GeoLocation: GeoLocation{35.4437, 139.6380}},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reference point A (Tokyo)
|
||||||
|
A := GeoLocation{35.6895, 139.6917}
|
||||||
|
|
||||||
|
// Sort by distance
|
||||||
|
B.SortByNearest(A)
|
||||||
|
|
||||||
|
// Print result
|
||||||
|
for _, loc := range B {
|
||||||
|
fmt.Printf("%s: %.2f km\n", loc.(City).Name, loc.Dist(A.Lat(), A.Lon())/1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
69
query/match_keys.go
Normal file
69
query/match_keys.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MatchKeys(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 {
|
||||||
|
anyFailed := false
|
||||||
|
for _, term := range terms {
|
||||||
|
if term.IsKey {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !((Key == "" || Key == *entry.GetKey()) && entry.Test(term.Value)) {
|
||||||
|
anyFailed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !anyFailed {
|
||||||
|
matches = append(matches, entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &QueryObject{Key: Key, Results: &matches, SearchTerms: &terms}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("Cannot parse: %s", line)
|
||||||
|
}
|
||||||
29
query/match_locations.go
Normal file
29
query/match_locations.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MatchNearest(p GeoLocation, entries *[]ISearchable, dist float64, limit int) (*QueryObject, error) {
|
||||||
|
|
||||||
|
terms := []*QTerm{
|
||||||
|
{
|
||||||
|
Org: fmt.Sprintf("%f, %f", p.Lat(), p.Lon()),
|
||||||
|
Value: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var locs *GeoLocations
|
||||||
|
|
||||||
|
locs = GeoLocations(*entries).Clean()
|
||||||
|
locs.SortByNearest(p)
|
||||||
|
|
||||||
|
matches := []ISearchable{}
|
||||||
|
for i, loc := range *locs {
|
||||||
|
if i < limit {
|
||||||
|
matches = append(matches, loc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &QueryObject{Key: "", Results: &matches, SearchTerms: &terms}, nil
|
||||||
|
}
|
||||||
27
query/objects.go
Normal file
27
query/objects.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package query
|
||||||
|
|
||||||
|
type QTerm struct {
|
||||||
|
Org string
|
||||||
|
Value string
|
||||||
|
IsKey bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryMessage struct {
|
||||||
|
Lang string
|
||||||
|
Text string
|
||||||
|
Location *GeoLocation
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryObject struct {
|
||||||
|
Key string
|
||||||
|
Message *QueryMessage
|
||||||
|
SearchTerms *[]*QTerm
|
||||||
|
Results *[]ISearchable
|
||||||
|
}
|
||||||
|
|
||||||
|
type IQueryResult interface {
|
||||||
|
Message() (string, error)
|
||||||
|
DataType() string
|
||||||
|
GetTableData() [][]map[string]string
|
||||||
|
Consumed() bool
|
||||||
|
}
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
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 {
|
|
||||||
anyFailed := false
|
|
||||||
for _, term := range terms {
|
|
||||||
if term.IsKey {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !( ( Key == "" || Key == *entry.GetKey() ) && entry.Test( term.Value ) ) {
|
|
||||||
anyFailed = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !anyFailed {
|
|
||||||
matches = append( matches, entry )
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &QueryObject{ Key: Key, Results: &matches, SearchTerms: &terms }, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf( "Cannot parse: %s", line )
|
|
||||||
}
|
|
||||||
@@ -5,75 +5,76 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type TestItem struct {
|
type TestItem struct {
|
||||||
Searchable
|
Words
|
||||||
|
NoGeoLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQuery( t *testing.T ) {
|
func TestQuery(t *testing.T) {
|
||||||
testItems := [] ISearchable{}
|
testItems := []ISearchable{}
|
||||||
|
|
||||||
s1234 := "1234"
|
s1234 := "1234"
|
||||||
sApple := "Apple"
|
sApple := "Apple"
|
||||||
s0000 := "0000"
|
s0000 := "0000"
|
||||||
|
|
||||||
sDat0 := "Dat0"
|
sDat0 := "Dat0"
|
||||||
data0 := [] *string{}
|
data0 := []*string{}
|
||||||
data0 = append( data0, &s1234 )
|
data0 = append(data0, &s1234)
|
||||||
data0 = append( data0, &sApple )
|
data0 = append(data0, &sApple)
|
||||||
t0 := TestItem{}
|
t0 := TestItem{}
|
||||||
t0.Key = &sDat0
|
t0.Key = &sDat0
|
||||||
t0.SearchData = &data0
|
t0.SearchData = &data0
|
||||||
testItems = append( testItems, &t0 )
|
testItems = append(testItems, &t0)
|
||||||
|
|
||||||
sDat1 := "Dat1"
|
sDat1 := "Dat1"
|
||||||
data1 := [] *string{}
|
data1 := []*string{}
|
||||||
data1 = append( data1, &s0000 )
|
data1 = append(data1, &s0000)
|
||||||
data1 = append( data1, &sApple )
|
data1 = append(data1, &sApple)
|
||||||
t1 := TestItem{}
|
t1 := TestItem{}
|
||||||
t1.Key = &sDat1
|
t1.Key = &sDat1
|
||||||
t1.SearchData = &data1
|
t1.SearchData = &data1
|
||||||
testItems = append( testItems, &t1 )
|
testItems = append(testItems, &t1)
|
||||||
|
|
||||||
sDat2 := "Dat2"
|
sDat2 := "Dat2"
|
||||||
data2 := [] *string{}
|
data2 := []*string{}
|
||||||
data2 = append( data2, &sDat0 )
|
data2 = append(data2, &sDat0)
|
||||||
t2 := TestItem{}
|
t2 := TestItem{}
|
||||||
t2.Key = &sDat2
|
t2.Key = &sDat2
|
||||||
t2.SearchData = &data2
|
t2.SearchData = &data2
|
||||||
testItems = append( testItems, &t2 )
|
testItems = append(testItems, &t2)
|
||||||
|
|
||||||
qo, err := Parse( "Apple", &testItems )
|
qo, err := MatchKeys("Apple", &testItems)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error( err )
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(*qo.Results) != 2 {
|
if len(*qo.Results) != 2 {
|
||||||
t.Error( "Expected 2 results when searching for \"Apple\"" )
|
t.Error("Expected 2 results when searching for \"Apple\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
qo, err = Parse( "0000", &testItems )
|
qo, err = MatchKeys("0000", &testItems)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error( err )
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(*qo.Results) != 1 {
|
if len(*qo.Results) != 1 {
|
||||||
t.Error( "Expected 1 results when searching for \"0000\"" )
|
t.Error("Expected 1 results when searching for \"0000\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
qo, err = Parse( "Dat0", &testItems )
|
qo, err = MatchKeys("Dat0", &testItems)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error( err )
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(*qo.Results) != 1 {
|
if len(*qo.Results) != 1 {
|
||||||
t.Error( "Expected 1 results when searching for \"Dat0\"" )
|
t.Error("Expected 1 results when searching for \"Dat0\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
qo, err = Parse( "Dat2 Dat0", &testItems )
|
qo, err = MatchKeys("Dat2 Dat0", &testItems)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error( err )
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(*qo.Results) != 1 {
|
if len(*qo.Results) != 1 {
|
||||||
t.Error( "Expected 1 results when searching for \"Dat2 Dat0\"" )
|
t.Error("Expected 1 results when searching for \"Dat2 Dat0\"")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
164
utils/system.go
164
utils/system.go
@@ -1,96 +1,100 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
"log"
|
"io"
|
||||||
"path/filepath"
|
"log"
|
||||||
"strings"
|
"path/filepath"
|
||||||
"time"
|
"strings"
|
||||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var JSON_SETTINGS string = filepath.Join( WORKDIR, "settings.json" )
|
var JSON_SETTINGS string = filepath.Join(WORKDIR, "settings.json")
|
||||||
|
|
||||||
var settingsTime = time.Unix( 0, 0 )
|
var settingsTime = time.Unix(0, 0)
|
||||||
|
|
||||||
type SysSettings struct {
|
type SysSettings struct {
|
||||||
IgnoredChats map[int64] bool `json:"IgnoredChats"`
|
IgnoredChats map[int64]bool `json:"IgnoredChats"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var Settings = SysSettings{ IgnoredChats: map[int64] bool{} }
|
var Settings = SysSettings{IgnoredChats: map[int64]bool{}}
|
||||||
|
|
||||||
func rwSettings() {
|
func rwSettings() {
|
||||||
|
|
||||||
buff, err := ChangedStream( JSON_SETTINGS, writeSettings, settingsTime )
|
buff, err := ChangedStream(JSON_SETTINGS, writeSettings, settingsTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic( err )
|
log.Panic(err)
|
||||||
return
|
return
|
||||||
}
|
|
||||||
|
|
||||||
conf := SysSettings{}
|
|
||||||
err = json.Unmarshal( buff.Bytes(), &conf )
|
|
||||||
if err != nil {
|
|
||||||
log.Panic( err )
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Settings = conf
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeSettings() ( io.Reader, error ) {
|
|
||||||
b, err := json.Marshal( Settings )
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return bytes.NewBuffer( b ), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SystemControl( tgMesg *tgbotapi.Message ) ( string, bool ) {
|
|
||||||
|
|
||||||
rwSettings()
|
|
||||||
|
|
||||||
processed := false
|
|
||||||
mesg := ""
|
|
||||||
|
|
||||||
if tgMesg.Text[0] == '/' {
|
|
||||||
processed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
chatId := tgMesg.Chat.ID
|
|
||||||
|
|
||||||
if Settings.IgnoredChats == nil {
|
|
||||||
Settings.IgnoredChats = map[int64] bool{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains( tgMesg.Text, "/golifehk disable" ) {
|
|
||||||
mesg = fmt.Sprintf( "OK" )
|
|
||||||
Settings.IgnoredChats[ chatId ] = true
|
|
||||||
processed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains( tgMesg.Text, "/golifehk enable" ) {
|
|
||||||
mesg = fmt.Sprintf( "OK" )
|
|
||||||
Settings.IgnoredChats[ chatId ] = false
|
|
||||||
processed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if processed {
|
|
||||||
settingsTime = time.Now()
|
|
||||||
rwSettings()
|
|
||||||
}
|
|
||||||
|
|
||||||
//// Begin processing settings
|
|
||||||
|
|
||||||
// ignore chats if enabled
|
|
||||||
if ignore, ok := Settings.IgnoredChats[ chatId ]; ok {
|
|
||||||
processed = processed || ignore;
|
|
||||||
} else {
|
|
||||||
// default ignore
|
|
||||||
processed = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return mesg, processed
|
conf := SysSettings{}
|
||||||
|
err = json.Unmarshal(buff.Bytes(), &conf)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Settings = conf
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeSettings() (io.Reader, error) {
|
||||||
|
b, err := json.Marshal(Settings)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bytes.NewBuffer(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SystemControl(tgMesg *tgbotapi.Message) (string, bool) {
|
||||||
|
|
||||||
|
rwSettings()
|
||||||
|
|
||||||
|
processed := false
|
||||||
|
mesg := ""
|
||||||
|
|
||||||
|
if tgMesg.Text == "" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
if tgMesg.Text[0] == '/' {
|
||||||
|
processed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
chatId := tgMesg.Chat.ID
|
||||||
|
|
||||||
|
if Settings.IgnoredChats == nil {
|
||||||
|
Settings.IgnoredChats = map[int64]bool{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(tgMesg.Text, "/golifehk disable") {
|
||||||
|
mesg = fmt.Sprintf("OK")
|
||||||
|
Settings.IgnoredChats[chatId] = true
|
||||||
|
processed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(tgMesg.Text, "/golifehk enable") {
|
||||||
|
mesg = fmt.Sprintf("OK")
|
||||||
|
Settings.IgnoredChats[chatId] = false
|
||||||
|
processed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if processed {
|
||||||
|
settingsTime = time.Now()
|
||||||
|
rwSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
//// Begin processing settings
|
||||||
|
|
||||||
|
// ignore chats if enabled
|
||||||
|
if ignore, ok := Settings.IgnoredChats[chatId]; ok {
|
||||||
|
processed = processed || ignore
|
||||||
|
} else {
|
||||||
|
// default ignore
|
||||||
|
processed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return mesg, processed
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user