commit 33a7c04e0980d21dd27610f97e372503388b8c43
| author | 斟酌 鵬兄 <tgckpg@gmail.com> |
| date | 2026-03-08T07:38:28Z |
| subject | Can now accept location |
commit 33a7c04e0980d21dd27610f97e372503388b8c43
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date: 2026-03-08T07:38:28Z
Can now accept location
---
adaptors/tg/botsend.go | 33 +++++++++++-----
datasources/cjlookup/QueryResult.go | 6 +--
datasources/kmb/BusStop.go | 62 +++++++++++++++++------------
datasources/kmb/QueryResult.go | 78 +++++++++++++++++++++++++++----------
datasources/kmb/RouteStop.go | 7 +---
datasources/kmb/query.go | 26 +++++++++++--
datasources/kmb/routestops.go | 66 +++++++++++++++++--------------
datasources/mtr/bus/QueryResult.go | 49 +++++++++++++++--------
main.go | 70 +++++++++++++++++++++------------
query/GeoLocation.go | 13 ++++++-
query/match_locations.go | 9 +++--
query/objects.go | 7 +++-
12 files changed, 282 insertions(+), 144 deletions(-)
diff --git a/adaptors/tg/botsend.go b/adaptors/tg/botsend.go
index 9885d8f..8229da2 100644
--- a/adaptors/tg/botsend.go
+++ b/adaptors/tg/botsend.go
@@ -1,6 +1,7 @@
package tg
import (
+ "fmt"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
query "github.com/tgckpg/golifehk/query"
)
@@ -32,25 +33,37 @@ func BotSend(bot *tgbotapi.BotAPI, update *tgbotapi.Update, qResult query.IQuery
return false, nil
}
- msg = tgbotapi.NewMessage(update.Message.Chat.ID, mesg)
+ var chatId int64
+ if update.Message != nil {
+ chatId = update.Message.Chat.ID
+ msg.ReplyToMessageID = update.Message.MessageID
+ }
+
+ if update.CallbackQuery != nil {
+ chatId = update.CallbackQuery.Message.Chat.ID
+ }
+
+ msg = tgbotapi.NewMessage(chatId, 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),
- )
+ buttonRows := [][]tgbotapi.InlineKeyboardButton{}
+ for _, row := range qResult.GetTableData() {
+ buttons := []tgbotapi.InlineKeyboardButton{}
+ for _, cell := range row {
+ button := tgbotapi.NewInlineKeyboardButtonData(cell.Name, cell.Value)
+ buttons = append(buttons, button)
+ fmt.Println(cell)
+ }
+ buttonRows = append(buttonRows, buttons)
+ }
+ msg.ReplyMarkup = tgbotapi.NewInlineKeyboardMarkup(buttonRows...)
}
- msg.ReplyToMessageID = update.Message.MessageID
bot.Send(msg)
return true, nil
}
diff --git a/datasources/cjlookup/QueryResult.go b/datasources/cjlookup/QueryResult.go
index 16a9272..976583c 100644
--- a/datasources/cjlookup/QueryResult.go
+++ b/datasources/cjlookup/QueryResult.go
@@ -40,9 +40,9 @@ 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) DataType() string { return this.ResultType }
+func (this QueryResult) Consumed() bool { return this.isConsumed }
+func (this QueryResult) GetTableData() [][]query.TableCell { return nil }
func (this QueryResult) Message() (string, error) {
diff --git a/datasources/kmb/BusStop.go b/datasources/kmb/BusStop.go
index 0019705..79b7ffe 100644
--- a/datasources/kmb/BusStop.go
+++ b/datasources/kmb/BusStop.go
@@ -1,40 +1,52 @@
package kmb
import (
- i18n "github.com/tgckpg/golifehk/i18n"
+ i18n "github.com/tgckpg/golifehk/i18n"
+ query "github.com/tgckpg/golifehk/query"
)
+type BusStopJson struct {
+ BusStopId string `json:"stop"`
+ Latitude float64 `json:"lat,string"`
+ Longitude float64 `json:"long,string"`
+ Name_en string `json:"name_en"`
+ Name_tc string `json:"name_tc"`
+ Name_sc string `json:"name_sc"`
+}
+
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"`
+ BusStopId string
- // Routes[ Route ][ Direction ]
- Routes *[] *RouteStop
+ // Routes[ Route ][ Direction ]
+ Routes *[]*RouteStop
+
+ i18n.Generics
+ query.GeoLocation
+}
- i18n.Generics
+type BusStopsJson struct {
+ Type string `json:"type"`
+ Version string `json:"version"`
+ DateCreated string `json:"generated_timestamp"`
+ BusStops []*BusStopJson `json:"data"`
}
-type BusStops struct {
- Type string `json:"type"`
- Version string `json:"version"`
- DateCreated string `json:"generated_timestamp"`
- BusStops [] *BusStop `json:"data"`
+func (b BusStop) Register(registers map[string]struct{}) bool {
+ if _, ok := registers[b.BusStopId]; ok {
+ return false
+ }
+ registers[b.BusStopId] = struct{}{}
+ return true
}
-func ( this *BusStop ) Reload() {
- i18n_Name := map[string] string{}
- i18n_Name["en"] = this.Name_en
- i18n_Name["zh-Hant"] = this.Name_tc
+func (this *BusStop) Reload() {
+ searchData := []*string{}
+ searchData = append(searchData, &this.BusStopId)
- searchData := [] *string{}
- searchData = append( searchData, &this.Name_en )
- searchData = append( searchData, &this.Name_tc )
+ for _, v := range *this.Name {
+ searchData = append(searchData, &v)
+ }
- this.Name = &i18n_Name
- this.Key = &this.BusStopId
- this.SearchData = &searchData
+ this.Key = &this.BusStopId
+ this.SearchData = &searchData
}
diff --git a/datasources/kmb/QueryResult.go b/datasources/kmb/QueryResult.go
index a1bdce0..730c411 100644
--- a/datasources/kmb/QueryResult.go
+++ b/datasources/kmb/QueryResult.go
@@ -17,6 +17,8 @@ type QueryResult struct {
Query *query.QueryObject
isConsumed bool
+ dataType string
+ tableData [][]query.TableCell
}
func writeRouteHead(sb *strings.Builder, r *RouteStop) {
@@ -50,12 +52,14 @@ func writeShortRoute(lang *string, sb *strings.Builder, r *RouteStop) {
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) DataType() string { return this.dataType }
+func (this QueryResult) Consumed() bool { return this.isConsumed }
+func (this QueryResult) GetTableData() [][]query.TableCell { return this.tableData }
func (this *QueryResult) Message() (string, error) {
+ this.dataType = "PlainText"
+
if this.Error != nil {
return "", this.Error
}
@@ -66,27 +70,61 @@ func (this *QueryResult) Message() (string, error) {
// Print Stop Names, then print the list of routes
if this.Query.Key == "" {
- busStops := map[string]*BusStop{}
- for _, item := range *this.Query.Results {
- var r *RouteStop
- r = any(item).(*RouteStop)
-
- b := r.BusStop
- if b.Routes == nil {
- continue
+ loc := this.Query.Message.Location
+ if loc != nil {
+ sb.WriteString("九巴 100m")
+ this.dataType = "Table"
+
+ table := [][]query.TableCell{}
+
+ for _, item := range *this.Query.Results {
+ b := any(item).(*BusStop)
+
+ row := []query.TableCell{}
+
+ cell := query.TableCell{
+ Name: fmt.Sprintf("%.2fm %s", b.Dist(loc.Lat(), loc.Lon()), (*b.Name)[this.Lang]),
+ Value: fmt.Sprintf("%s", b.BusStopId),
+ }
+ row = append(row, cell)
+
+ for _, r := range *b.Routes {
+ sb_i := strings.Builder{}
+ writeRouteHead(&sb_i, r)
+ cell := query.TableCell{
+ Name: sb_i.String(),
+ Value: fmt.Sprintf("%s %s", r.RouteId, (*b.Name)[this.Lang]),
+ }
+ row = append(row, cell)
+ }
+
+ table = append(table, row)
}
+ this.tableData = table
- busStops[b.BusStopId] = b
- }
+ } else {
+ busStops := map[string]*BusStop{}
+ for _, item := range *this.Query.Results {
+ var r *RouteStop
+ r = any(item).(*RouteStop)
+
+ b := r.BusStop
+ if b.Routes == nil {
+ continue
+ }
+
+ busStops[b.BusStopId] = b
+ }
- for _, b := range busStops {
- utils.WriteMDv2Text(&sb, (*b.Name)[this.Lang])
- sb.WriteString("\n ")
- for _, route := range *b.Routes {
- writeRouteHead(&sb, route)
- sb.WriteString(" ")
+ for _, b := range busStops {
+ utils.WriteMDv2Text(&sb, (*b.Name)[this.Lang])
+ sb.WriteString("\n ")
+ for _, route := range *b.Routes {
+ writeRouteHead(&sb, route)
+ sb.WriteString(" ")
+ }
+ sb.WriteString("\n")
}
- sb.WriteString("\n")
}
// We got a route key
diff --git a/datasources/kmb/RouteStop.go b/datasources/kmb/RouteStop.go
index d9554a4..6e2ec8c 100644
--- a/datasources/kmb/RouteStop.go
+++ b/datasources/kmb/RouteStop.go
@@ -40,13 +40,8 @@ func (routeStop RouteStop) NextStop() *RouteStop {
func (this *RouteStop) Reload() {
- searchData := []*string{}
- busStop := *this.BusStop
- searchData = append(searchData, &busStop.Name_en)
- searchData = append(searchData, &busStop.Name_tc)
-
this.Key = &this.RouteId
- this.SearchData = &searchData
+ this.SearchData = this.BusStop.SearchData
}
type ByRoute []*RouteStop
diff --git a/datasources/kmb/query.go b/datasources/kmb/query.go
index c6b18d2..ba04adc 100644
--- a/datasources/kmb/query.go
+++ b/datasources/kmb/query.go
@@ -9,19 +9,39 @@ import (
func Query(q query.QueryMessage) query.IQueryResult {
lang := q.Lang
- message := q.Text
var qo *query.QueryObject
var err error
+ var routeStops *[]query.ISearchable
qr := QueryResult{Lang: lang}
- routeStops, err := getRouteStops()
+
+ busStops, err := readBusStopsData()
+ if err != nil {
+ qr.Error = err
+ goto qrReturn
+ }
+
+ routeStops, err = getRouteStops(busStops)
if err != nil {
qr.Error = err
goto qrReturn
}
- qo, err = query.MatchKeys(strings.ToUpper(message), routeStops)
+ if q.Text != "" {
+ qo, err = query.MatchKeys(strings.ToUpper(q.Text), routeStops)
+ } else if q.Location != nil {
+ bList := []query.ISearchable{}
+
+ for _, b := range *busStops {
+ bList = append(bList, b)
+ }
+
+ qo, err = query.MatchNearest(*q.Location, &bList, 100, 3)
+ }
+
+ qo.Message = &q
+
if err != nil {
qr.Error = err
goto qrReturn
diff --git a/datasources/kmb/routestops.go b/datasources/kmb/routestops.go
index 2bfb63a..1e0dcc0 100644
--- a/datasources/kmb/routestops.go
+++ b/datasources/kmb/routestops.go
@@ -32,7 +32,6 @@ func readRouteStopsData(busStops *map[string]*BusStop, buff *bytes.Buffer) (*[]*
if busStop == nil {
busStop = &BusStop{
BusStopId: entry.StationId,
- Name_en: "???", Name_tc: "???", Name_sc: "???",
}
busStop.Reload()
@@ -76,37 +75,15 @@ func readRouteStopsData(busStops *map[string]*BusStop, buff *bytes.Buffer) (*[]*
return &allRouteStops, 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 getRouteStops() (*[]query.ISearchable, error) {
- var busStopMap *map[string]*BusStop
+func readBusStopsData() (*map[string]*BusStop, error) {
+ busStopsData := BusStopsJson{}
QUERY_FUNC := func() (io.ReadCloser, error) {
return utils.HttpGet("https://data.etabus.gov.hk/v1/transport/kmb/stop")
}
PARSE_FUNC := func(buff *bytes.Buffer) error {
- var err error
- busStopMap, err = readBusStopsData(buff)
+ err := json.Unmarshal(buff.Bytes(), &busStopsData)
return err
}
@@ -120,18 +97,49 @@ func getRouteStops() (*[]query.ISearchable, error) {
return nil, err
}
+ busStopMap := map[string]*BusStop{}
+ for _, entry := range busStopsData.BusStops {
+
+ b := BusStop{
+ BusStopId: entry.BusStopId,
+ }
+
+ b.Latitude = entry.Latitude
+ b.Longitude = entry.Longitude
+
+ n := map[string]string{
+ "en": entry.Name_en,
+ "zh-Hant": entry.Name_tc,
+ "zh-Hans": entry.Name_sc,
+ }
+
+ b.Name = &n
+
+ b.Reload()
+
+ if _, ok := busStopMap[b.BusStopId]; ok {
+ return nil, fmt.Errorf("Duplicated BusStop: %s", b.BusStopId)
+ }
+
+ busStopMap[b.BusStopId] = &b
+ }
+ return &busStopMap, nil
+}
+
+func getRouteStops(busStopMap *map[string]*BusStop) (*[]query.ISearchable, error) {
var routeStops *[]*RouteStop
- QUERY_FUNC = func() (io.ReadCloser, error) {
+
+ QUERY_FUNC := func() (io.ReadCloser, error) {
return utils.HttpGet("https://data.etabus.gov.hk/v1/transport/kmb/route-stop")
}
- PARSE_FUNC = func(buff *bytes.Buffer) error {
+ PARSE_FUNC := func(buff *bytes.Buffer) error {
var err error
routeStops, err = readRouteStopsData(busStopMap, buff)
return err
}
- cs, err = utils.CacheStreamEx(JSON_ROUTESTOPS, QUERY_FUNC)
+ cs, err := utils.CacheStreamEx(JSON_ROUTESTOPS, QUERY_FUNC)
if err != nil {
return nil, err
}
diff --git a/datasources/mtr/bus/QueryResult.go b/datasources/mtr/bus/QueryResult.go
index 0434b9d..87556aa 100644
--- a/datasources/mtr/bus/QueryResult.go
+++ b/datasources/mtr/bus/QueryResult.go
@@ -18,6 +18,8 @@ type QueryResult struct {
Query *query.QueryObject
isConsumed bool
+ dataType string
+ tableData [][]query.TableCell
}
func writeShortRoute(lang *string, sb *strings.Builder, b *BusStop) {
@@ -38,11 +40,13 @@ func writeShortRoute(lang *string, sb *strings.Builder, b *BusStop) {
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) DataType() string { return this.dataType }
+func (this QueryResult) Consumed() bool { return this.isConsumed }
+func (this QueryResult) GetTableData() [][]query.TableCell { return this.tableData }
-func (this QueryResult) Message() (string, error) {
+func (this *QueryResult) Message() (string, error) {
+
+ this.dataType = "PlainText"
if this.Error != nil {
return "", this.Error
@@ -67,29 +71,40 @@ func (this QueryResult) Message() (string, error) {
loc := q.Message.Location
if loc != nil {
- // Print nearest bus stops
+ this.dataType = "Table"
+ sb.WriteString("K巴 100m")
+
+ table := [][]query.TableCell{}
+
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)
+ sb_i := strings.Builder{}
+ sb_i.WriteString(fmt.Sprintf("%.2fm", busStop.Dist(loc.Lat(), loc.Lon())))
+ sb_i.WriteString(" ")
+ utils.WriteMDv2Text(&sb_i, busStop.RouteId)
d := busStop.Direction
if d == "O" {
- sb.WriteString("↑")
+ sb_i.WriteString("↑")
} else if d == "I" {
- sb.WriteString("↓")
+ sb_i.WriteString("↓")
} else {
- sb.WriteString("\\?")
+ sb_i.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")
+ sb_i.WriteString(" ")
+ utils.WriteMDv2Text(&sb_i, (*busStop.Name)[this.Lang])
+
+ row := []query.TableCell{
+ query.TableCell{
+ Name: sb_i.String(),
+ Value: fmt.Sprintf("%s %s", busStop.RouteId, (*busStop.Name)[this.Lang]),
+ },
+ }
+
+ table = append(table, row)
}
+ this.tableData = table
} else {
sort.Sort(query.ByKey(*q.Results))
for _, entry := range *q.Results {
diff --git a/main.go b/main.go
index 2bf2ba7..f32b85f 100644
--- a/main.go
+++ b/main.go
@@ -30,6 +30,22 @@ func main() {
updates := bot.GetUpdatesChan(u)
for update := range updates {
+
+ if update.CallbackQuery != nil {
+ callback := tgbotapi.NewCallback(update.CallbackQuery.ID, "")
+ bot.Request(callback)
+
+ q := query.QueryMessage{Lang: "zh-Hant", Text: update.CallbackQuery.Data}
+ f_sent, f_err := processQuery(bot, &update, q)
+
+ if !f_sent && f_err != nil {
+ mesg := utils.MDv2Text(fmt.Sprintf("%s", f_err))
+ tgadaptor.BotSendText(bot, &update, &mesg)
+ }
+
+ continue
+ }
+
if update.Message == nil {
continue
}
@@ -45,15 +61,6 @@ func main() {
continue
}
- f_queries := []func(query.QueryMessage) query.IQueryResult{
- cjlookup.Query,
- mtrbus.Query,
- kmb.Query,
- }
-
- var f_sent bool = false
- var f_err error = nil
-
tgMesg := update.Message
q := query.QueryMessage{Lang: "zh-Hant", Text: tgMesg.Text}
@@ -61,27 +68,42 @@ func main() {
q.Location = &query.GeoLocation{tgMesg.Location.Latitude, tgMesg.Location.Longitude}
}
- for _, Query := range f_queries {
+ f_sent, f_err := processQuery(bot, &update, q)
+
+ if !isGroup && !f_sent && f_err != nil {
+ mesg := utils.MDv2Text(fmt.Sprintf("%s", f_err))
+ tgadaptor.BotSendText(bot, &update, &mesg)
+ }
+ }
+}
+
+func processQuery(bot *tgbotapi.BotAPI, update *tgbotapi.Update, q query.QueryMessage) (bool, error) {
+ var f_sent bool = false
+ var f_err error = nil
- qResult := Query(q)
- sent, err := tgadaptor.BotSend(bot, &update, qResult)
+ f_queries := []func(query.QueryMessage) query.IQueryResult{
+ cjlookup.Query,
+ mtrbus.Query,
+ kmb.Query,
+ }
- if sent {
- f_sent = true
- }
+ for _, Query := range f_queries {
- if err != nil {
- f_err = err
- }
+ qResult := Query(q)
+ sent, err := tgadaptor.BotSend(bot, update, qResult)
- if qResult.Consumed() {
- break
- }
+ if sent {
+ f_sent = true
}
- if !isGroup && !f_sent && f_err != nil {
- mesg := utils.MDv2Text(fmt.Sprintf("%s", f_err))
- tgadaptor.BotSendText(bot, &update, &mesg)
+ if err != nil {
+ f_err = err
+ }
+
+ if qResult.Consumed() {
+ break
}
}
+
+ return f_sent, f_err
}
diff --git a/query/GeoLocation.go b/query/GeoLocation.go
index db8f654..30c5800 100644
--- a/query/GeoLocation.go
+++ b/query/GeoLocation.go
@@ -5,6 +5,14 @@ import (
"sort"
)
+type IGeoLocation interface {
+ HasGeoLocation() bool
+ Lat() float64
+ Lon() float64
+ Dist(lat, lon float64) float64
+ Register(map[string]struct{}) bool
+}
+
type GeoLocation struct {
Latitude float64
Longitude float64
@@ -19,7 +27,7 @@ func (b GeoLocation) Dist(lat float64, lon float64) float64 {
var dist float64
geodesic.WGS84.Inverse(
lat, lon,
- b.Latitude, b.Longitude,
+ b.Lat(), b.Lon(),
&dist, nil, nil,
)
return dist
@@ -37,13 +45,14 @@ func (b NoGeoLocation) Register(map[string]struct{}) bool {
type GeoLocations []ISearchable
-func (m GeoLocations) SortByNearest(p GeoLocation) {
+func (m GeoLocations) SortByNearest(p IGeoLocation) {
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 {
+ panic("GeoLocation: Default is called")
return false
}
diff --git a/query/match_locations.go b/query/match_locations.go
index 7fe39f0..96f07ad 100644
--- a/query/match_locations.go
+++ b/query/match_locations.go
@@ -4,7 +4,7 @@ import (
"fmt"
)
-func MatchNearest(p GeoLocation, entries *[]ISearchable, dist float64, limit int) (*QueryObject, error) {
+func MatchNearest(p IGeoLocation, entries *[]ISearchable, dist float64, limit int) (*QueryObject, error) {
terms := []*QTerm{
{
@@ -19,9 +19,10 @@ func MatchNearest(p GeoLocation, entries *[]ISearchable, dist float64, limit int
locs.SortByNearest(p)
matches := []ISearchable{}
- for i, loc := range *locs {
- if i < limit {
- matches = append(matches, loc)
+ for i, item := range *locs {
+ loc := item.(IGeoLocation)
+ if i < limit && loc.Dist(p.Lat(), p.Lon()) <= dist {
+ matches = append(matches, item)
}
}
diff --git a/query/objects.go b/query/objects.go
index bcbe66a..50120e0 100644
--- a/query/objects.go
+++ b/query/objects.go
@@ -19,9 +19,14 @@ type QueryObject struct {
Results *[]ISearchable
}
+type TableCell struct {
+ Name string
+ Value string
+}
+
type IQueryResult interface {
Message() (string, error)
DataType() string
- GetTableData() [][]map[string]string
+ GetTableData() [][]TableCell
Consumed() bool
}