Refactoring codes for more tg message types

This commit is contained in:
2026-03-07 22:16:14 +08:00
parent a396a381b5
commit 912f9fd0ad
26 changed files with 771 additions and 472 deletions

56
adaptors/tg/botsend.go Normal file
View 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
}

View File

@@ -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 {

View File

@@ -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{}

View File

@@ -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

View File

@@ -15,6 +15,8 @@ type QueryResult struct {
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) {
@@ -48,6 +50,10 @@ func writeShortRoute( lang *string, sb *strings.Builder, r *RouteStop ) {
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 {
@@ -165,7 +171,6 @@ func ( this *QueryResult ) Message() ( string, error ) {
sb.WriteString("\n") sb.WriteString("\n")
} }
} }
} }
} else { } else {

View File

@@ -13,7 +13,8 @@ type RouteStop struct {
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 {

View File

@@ -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

View File

@@ -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) }

View File

@@ -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,6 +63,34 @@ func (this QueryResult) Message() (string, error) {
} }
if q.Key == "" { if q.Key == "" {
loc := q.Message.Location
if loc != nil {
// Print nearest bus stops
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)) sort.Sort(query.ByKey(*q.Results))
for _, entry := range *q.Results { for _, entry := range *q.Results {
busStop := any(entry).(*BusStop) busStop := any(entry).(*BusStop)
@@ -63,6 +98,7 @@ func (this QueryResult) Message() (string, error) {
sb.WriteString(" ") sb.WriteString(" ")
writeShortRoute(&this.Lang, &sb, busStop) writeShortRoute(&this.Lang, &sb, busStop)
} }
}
} else if 1 == len(*q.SearchTerms) { } else if 1 == len(*q.SearchTerms) {
// Route listing // Route listing

View File

@@ -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":

View File

@@ -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
View File

@@ -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
View File

@@ -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=

View File

@@ -6,5 +6,5 @@ import (
type Generics struct { type Generics struct {
Name *map[string]string Name *map[string]string
query.Searchable query.Words
} }

42
main.go
View File

@@ -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
View 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
}

View File

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

View File

@@ -1,40 +1,15 @@
package query package query
import (
"strings"
)
type ISearchable interface { type ISearchable interface {
HasWords() bool
Test(string) bool Test(string) bool
GetKey() *string GetKey() *string
}
type Searchable struct { HasGeoLocation() bool
Key *string Lat() float64
SearchData *[] *string Lon() float64
} Dist(lat float64, lon float64) float64
func ( this *Searchable ) Test( val string ) bool { // Clean() filtering
Register(map[string]struct{}) 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
View 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
View 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
View 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
View 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
View 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
}

View File

@@ -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 )
}

View File

@@ -5,7 +5,8 @@ import (
) )
type TestItem struct { type TestItem struct {
Searchable Words
NoGeoLocation
} }
func TestQuery(t *testing.T) { func TestQuery(t *testing.T) {
@@ -41,7 +42,7 @@ func TestQuery( t *testing.T ) {
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)
} }
@@ -50,7 +51,7 @@ func TestQuery( t *testing.T ) {
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)
} }
@@ -59,7 +60,7 @@ func TestQuery( t *testing.T ) {
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)
} }
@@ -68,7 +69,7 @@ func TestQuery( t *testing.T ) {
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)
} }

View File

@@ -4,12 +4,12 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"io" "io"
"log" "log"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
) )
var JSON_SETTINGS string = filepath.Join(WORKDIR, "settings.json") var JSON_SETTINGS string = filepath.Join(WORKDIR, "settings.json")
@@ -55,6 +55,10 @@ func SystemControl( tgMesg *tgbotapi.Message ) ( string, bool ) {
processed := false processed := false
mesg := "" mesg := ""
if tgMesg.Text == "" {
return "", false
}
if tgMesg.Text[0] == '/' { if tgMesg.Text[0] == '/' {
processed = true processed = true
} }
@@ -86,10 +90,10 @@ func SystemControl( tgMesg *tgbotapi.Message ) ( string, bool ) {
// ignore chats if enabled // ignore chats if enabled
if ignore, ok := Settings.IgnoredChats[chatId]; ok { if ignore, ok := Settings.IgnoredChats[chatId]; ok {
processed = processed || ignore; processed = processed || ignore
} else { } else {
// default ignore // default ignore
processed = true; processed = true
} }
return mesg, processed return mesg, processed