Skip to content

Commit d1e8396

Browse files
committed
feat: Add cel-go math evaluator to inline query
1 parent 24d542d commit d1e8396

File tree

7 files changed

+95
-14
lines changed

7 files changed

+95
-14
lines changed

cmd/serve.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/itsamirhn/dongetobede/internal/bot"
1414
"github.com/itsamirhn/dongetobede/internal/config"
1515
"github.com/itsamirhn/dongetobede/internal/database"
16+
"github.com/itsamirhn/dongetobede/internal/expression"
1617
)
1718

1819
func init() {
@@ -50,7 +51,9 @@ func serve(cmd *cobra.Command, _ []string) {
5051
}
5152

5253
func createBotOrPanic(db database.Client) *bot.Bot {
53-
b, err := bot.NewBot(db, config.GlobalConfig.Token, config.GlobalConfig.Endpoint, config.GlobalConfig.ListenPort)
54+
evaluator := expression.NewCelEvaluator()
55+
b, err := bot.NewBot(db, evaluator,
56+
config.GlobalConfig.Token, config.GlobalConfig.Endpoint, config.GlobalConfig.ListenPort)
5457
if err != nil {
5558
logrus.WithError(err).Panic("failed to create bot")
5659
}

go.mod

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
module github.com/itsamirhn/dongetobede
1+
module github.com/itsamirhn/dongetobede
22

33
go 1.22
44

55
require (
6+
github.com/google/cel-go v0.20.1
67
github.com/google/uuid v1.6.0
78
github.com/mavihq/persian v0.0.0-20231020110200-3e779b10be51
89
github.com/pkg/errors v0.9.1
@@ -14,6 +15,7 @@ require (
1415
)
1516

1617
require (
18+
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
1719
github.com/fsnotify/fsnotify v1.7.0 // indirect
1820
github.com/golang/snappy v0.0.4 // indirect
1921
github.com/hashicorp/hcl v1.0.0 // indirect
@@ -29,6 +31,7 @@ require (
2931
github.com/spf13/afero v1.11.0 // indirect
3032
github.com/spf13/cast v1.6.0 // indirect
3133
github.com/spf13/pflag v1.0.5 // indirect
34+
github.com/stoewer/go-strcase v1.2.0 // indirect
3235
github.com/subosito/gotenv v1.6.0 // indirect
3336
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
3437
github.com/xdg-go/scram v1.1.2 // indirect
@@ -40,6 +43,9 @@ require (
4043
golang.org/x/sync v0.7.0 // indirect
4144
golang.org/x/sys v0.19.0 // indirect
4245
golang.org/x/text v0.14.0 // indirect
46+
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect
47+
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
48+
google.golang.org/protobuf v1.31.0 // indirect
4349
gopkg.in/ini.v1 v1.67.0 // indirect
4450
gopkg.in/yaml.v3 v3.0.1 // indirect
4551
)

go.sum

+12-2
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
6464
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
6565
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
6666
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
67+
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
68+
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
6769
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
6870
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
6971
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
@@ -175,6 +177,8 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
175177
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
176178
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
177179
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
180+
github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84=
181+
github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg=
178182
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
179183
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
180184
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -392,6 +396,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
392396
github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw=
393397
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
394398
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
399+
github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
400+
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
395401
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
396402
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
397403
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
@@ -545,8 +551,6 @@ golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su
545551
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
546552
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
547553
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
548-
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
549-
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
550554
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
551555
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
552556
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -866,6 +870,10 @@ google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX
866870
google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
867871
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
868872
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
873+
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo=
874+
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4=
875+
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I=
876+
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc=
869877
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
870878
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
871879
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -911,6 +919,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
911919
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
912920
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
913921
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
922+
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
923+
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
914924
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
915925
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
916926
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

internal/bot/bot.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,17 @@ import (
1010
"github.com/itsamirhn/dongetobede/internal/bot/middleware"
1111
"github.com/itsamirhn/dongetobede/internal/config"
1212
"github.com/itsamirhn/dongetobede/internal/database"
13+
"github.com/itsamirhn/dongetobede/internal/expression"
1314
"github.com/itsamirhn/dongetobede/pkg"
1415
)
1516

1617
type Bot struct {
1718
*telebot.Bot
1819
}
1920

20-
func NewBot(db database.Client, token string, endpoint string, listenPort string) (*Bot, error) {
21+
func NewBot(db database.Client, evaluator expression.Evaluator,
22+
token string, endpoint string, listenPort string,
23+
) (*Bot, error) {
2124
settings := telebot.Settings{
2225
Token: token,
2326
Poller: getTelebotPoller(endpoint, listenPort),
@@ -33,7 +36,7 @@ func NewBot(db database.Client, token string, endpoint string, listenPort string
3336
bot.registerCommands([]handler.Command{
3437
handler.NewStart(db),
3538
handler.NewSetCard(db),
36-
handler.NewQuery(db),
39+
handler.NewQuery(db, evaluator),
3740
handler.NewInline(db),
3841
handler.NewCallback(db),
3942
handler.NewText(db),

internal/bot/handler/query.go

+14-8
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,26 @@ package handler
22

33
import (
44
"fmt"
5-
"strconv"
5+
"math"
66

77
"github.com/mavihq/persian"
88
"gopkg.in/telebot.v3"
99

1010
"github.com/itsamirhn/dongetobede/internal/database"
1111
"github.com/itsamirhn/dongetobede/internal/database/entities"
12+
"github.com/itsamirhn/dongetobede/internal/expression"
1213
)
1314

1415
type query struct {
15-
db database.Client
16+
db database.Client
17+
evaluator expression.Evaluator
1618
}
1719

18-
func NewQuery(db database.Client) Command {
19-
return &query{db: db}
20+
func NewQuery(db database.Client, evaluator expression.Evaluator) Command {
21+
return &query{
22+
db: db,
23+
evaluator: evaluator,
24+
}
2025
}
2126

2227
func (c *query) Endpoint() string {
@@ -42,11 +47,11 @@ func (c *query) getInvalidArticle() *telebot.ArticleResult {
4247
res := &telebot.ArticleResult{
4348
Title: "مبلغ نامعتبر",
4449
Description: "مجموع هزینه را به تومان وارد کنید.",
45-
Text: ` مبلغ نامعتبر است. لطفا مبلغ را به صورت عددی وارد کنید.`,
50+
Text: ` مبلغ نامعتبر است. لطفا مبلغ را به صورت عددی یا یک عبارت ریاضی غیر اعشاری وارد کنید.`,
4651
}
4752
markup := &telebot.ReplyMarkup{}
4853
markup.Inline(
49-
markup.Row(markup.QueryChat("مثال", "56000")),
54+
markup.Row(markup.QueryChat("مثال", "56000 + 12000")),
5055
)
5156
res.SetReplyMarkup(markup)
5257
res.SetParseMode(telebot.ModeMarkdown)
@@ -63,8 +68,9 @@ func (c *query) getValidArticles(amount int, cardNumber string) []telebot.Result
6368

6469
func (c *query) Handle(ctx telebot.Context) error {
6570
q := ctx.Query().Text
66-
amountStr := persian.ToEnglishDigits(q)
67-
amount, err := strconv.Atoi(amountStr)
71+
expressionStr := persian.ToEnglishDigits(q)
72+
amountFloat, err := c.evaluator.Eval(expressionStr)
73+
amount := int(math.Round(amountFloat))
6874
if amount <= 0 || err != nil {
6975
return ctx.Answer(&telebot.QueryResponse{
7076
Results: []telebot.Result{c.getInvalidArticle()},

internal/expression/cel.go

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package expression
2+
3+
import (
4+
"github.com/google/cel-go/cel"
5+
)
6+
7+
type celEvaluator struct {
8+
env *cel.Env
9+
}
10+
11+
func NewCelEvaluator() Evaluator {
12+
env, err := cel.NewEnv()
13+
if err != nil {
14+
panic(err)
15+
}
16+
return &celEvaluator{
17+
env: env,
18+
}
19+
}
20+
21+
func (c celEvaluator) Eval(expr string) (float64, error) {
22+
ast, iss := c.env.Parse(expr)
23+
if iss != nil && iss.Err() != nil {
24+
return 0, iss.Err()
25+
}
26+
prg, err := c.env.Program(ast)
27+
if err != nil {
28+
return 0, err
29+
}
30+
out, _, err := prg.Eval(map[string]interface{}{})
31+
if err != nil {
32+
return 0, err
33+
}
34+
switch out.Type() {
35+
case cel.DoubleType:
36+
return out.Value().(float64), nil
37+
case cel.UintType:
38+
return float64(out.Value().(uint)), nil
39+
case cel.IntType:
40+
return float64(out.Value().(int64)), nil
41+
default:
42+
return 0, ErrInvalidExpression
43+
}
44+
}

internal/expression/evaluator.go

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package expression
2+
3+
import "github.com/pkg/errors"
4+
5+
var ErrInvalidExpression = errors.New("invalid expression")
6+
7+
type Evaluator interface {
8+
Eval(expr string) (float64, error)
9+
}

0 commit comments

Comments
 (0)