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

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
import (
"strings"
)
type ISearchable interface {
Test( string ) bool
GetKey() *string
HasWords() bool
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
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,75 +5,76 @@ import (
)
type TestItem struct {
Searchable
Words
NoGeoLocation
}
func TestQuery( t *testing.T ) {
testItems := [] ISearchable{}
func TestQuery(t *testing.T) {
testItems := []ISearchable{}
s1234 := "1234"
sApple := "Apple"
s0000 := "0000"
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 )
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 )
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 )
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 )
}
qo, err := MatchKeys("Apple", &testItems)
if err != nil {
t.Error(err)
}
if len(*qo.Results) != 2 {
t.Error( "Expected 2 results when searching for \"Apple\"" )
}
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 )
}
qo, err = MatchKeys("0000", &testItems)
if err != nil {
t.Error(err)
}
if len(*qo.Results) != 1 {
t.Error( "Expected 1 results when searching for \"0000\"" )
}
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 )
}
qo, err = MatchKeys("Dat0", &testItems)
if err != nil {
t.Error(err)
}
if len(*qo.Results) != 1 {
t.Error( "Expected 1 results when searching for \"Dat0\"" )
}
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 )
}
qo, err = MatchKeys("Dat2 Dat0", &testItems)
if err != nil {
t.Error(err)
}
if len(*qo.Results) != 1 {
t.Error( "Expected 1 results when searching for \"Dat2 Dat0\"" )
}
if len(*qo.Results) != 1 {
t.Error("Expected 1 results when searching for \"Dat2 Dat0\"")
}
}