From cd965fd81156bb10ab8fb5379ec92a4b201aa9ce Mon Sep 17 00:00:00 2001 From: surdeus Date: Fri, 11 Aug 2023 10:41:17 +0300 Subject: [PATCH] Implemented synchronous reading from the client letting us read messages. --- cmd/test/main.go | 93 ++++++++++++++++++++++++++++++----------------- src/tx/bot.go | 1 + src/tx/context.go | 88 ++++++++++++++++++++++++++++++++++++++------ src/tx/errors.go | 13 +++++++ src/tx/screen.go | 12 ++++++ src/tx/session.go | 5 ++- 6 files changed, 165 insertions(+), 47 deletions(-) diff --git a/cmd/test/main.go b/cmd/test/main.go index 0418ee7..5dc3038 100644 --- a/cmd/test/main.go +++ b/cmd/test/main.go @@ -3,51 +3,76 @@ package main import ( "log" "os" + "strings" "github.com/mojosa-software/got/src/tx" ) -var navKeyboard = tx.NewKeyboard("nav").Row( - tx.NewButton().WithText("Inc/Dec").ScreenChange("inc/dec"), -).Row( - tx.NewButton().WithText("Upper case").ScreenChange("upper-case"), - tx.NewButton().WithText("Lower case").ScreenChange("lower-case"), -) - -var incKeyboard = tx.NewKeyboard("inc/dec").Row( - tx.NewButton().WithText("+").ActionFunc(func(c *tx.Context) { - counter := c.V["counter"].(*int) - *counter++ - c.Sendf("%d", *counter) - }), - tx.NewButton().WithText("-").ActionFunc(func(c *tx.Context) { - counter := c.V["counter"].(*int) - *counter-- - c.Sendf("%d", *counter) - }), -) - -var startScreen = tx.NewScreen("start"). - WithText("The bot started!"). - Keyboard("nav") - -var incScreen = tx.NewScreen("inc/dec"). - WithText("The screen shows how user separated data works"). - IKeyboard("inc/dec"). - Keyboard("nav") +var startScreenButton = tx.NewButton(). + WithText("🏠 To the start screen"). + ScreenChange("start") var beh = tx.NewBehaviour(). + // The function will be called every time + // the bot is started. OnStartFunc(func(c *tx.Context) { - // The function will be called every time - // the bot is started. c.V["counter"] = new(int) c.ChangeScreen("start") }).WithKeyboards( - navKeyboard, - incKeyboard, + // Increment/decrement keyboard. + tx.NewKeyboard("inc/dec").Row( + tx.NewButton().WithText("+").ActionFunc(func(c *tx.Context) { + counter := c.V["counter"].(*int) + *counter++ + c.Sendf("%d", *counter) + }), + tx.NewButton().WithText("-").ActionFunc(func(c *tx.Context) { + counter := c.V["counter"].(*int) + *counter-- + c.Sendf("%d", *counter) + }), + ).Row( + startScreenButton, + ), + // The navigational keyboard. + tx.NewKeyboard("nav").Row( + tx.NewButton().WithText("Inc/Dec").ScreenChange("inc/dec"), + ).Row( + tx.NewButton().WithText("Upper case").ScreenChange("upper-case"), + tx.NewButton().WithText("Lower case").ScreenChange("lower-case"), + ), + // The keyboard to return to the start screen. + tx.NewKeyboard("nav-start").Row( + startScreenButton, + ), ).WithScreens( - startScreen, - incScreen, + tx.NewScreen("start"). + WithText("The bot started!"). + Keyboard("nav"), + tx.NewScreen("inc/dec"). + WithText( + "The screen shows how"+ + "user separated data works"+ + "by saving the counter for each of them", + ). + Keyboard("inc/dec"). + // The function will be called when reaching the screen. + ActionFunc(func(c *tx.Context) { + counter := c.V["counter"].(*int) + c.Sendf("Current counter value equals %d", *counter) + }), + tx.NewScreen("upper-case"). + WithText("Type text and the bot will send you the upper case version to you"). + Keyboard("nav-start"). + ActionFunc(func(c *tx.Context) { + for { + s, err := c.ReadTextMessage() + if err == tx.NotAvailableErr { + break + } + c.Sendf("%s", strings.ToUpper(s)) + } + }), ) func main() { diff --git a/src/tx/bot.go b/src/tx/bot.go index 2c264be..c1d8b86 100644 --- a/src/tx/bot.go +++ b/src/tx/bot.go @@ -61,6 +61,7 @@ func (bot *Bot) Run() error { ctx := &Context{ B: bot, Session: session, + updates: make(chan *Update), } chn := make(chan *Update) diff --git a/src/tx/context.go b/src/tx/context.go index 238f8fb..e112b1b 100644 --- a/src/tx/context.go +++ b/src/tx/context.go @@ -10,17 +10,19 @@ import ( // handling functions. Is provided to Act() function always. type Context struct { *Session - B *Bot + B *Bot + updates chan *Update + available bool } // Goroutie function to handle each user. -func (ctx *Context) handleUpdateChan(updates chan *Update) { - bot := ctx.B - session := ctx.Session - bot.Start.Act(ctx) +func (c *Context) handleUpdateChan(updates chan *Update) { + bot := c.B + session := c.Session + bot.Start.Act(c) for u := range updates { screen := bot.Screens[session.CurrentScreenId] - + // The part is added to implement custom update handling. if u.Message != nil { kbd := bot.Keyboards[screen.KeyboardId] @@ -28,12 +30,26 @@ func (ctx *Context) handleUpdateChan(updates chan *Update) { text := u.Message.Text btn, ok := btns[text] - // Skipping wrong text messages. - if !ok { + /*if ok { + c.available = false + btn.Action.Act(c) + c.available = true + continue + }*/ + + // Sending wrong messages to + // the currently reading goroutine. + if !ok && c.ReadingUpdate { + c.updates <- u continue } - btn.Action.Act(ctx) + if !ok { + } + + if ok { + c.run(btn.Action) + } } else if u.CallbackQuery != nil { cb := apix.NewCallback(u.CallbackQuery.ID, u.CallbackQuery.Data) data := u.CallbackQuery.Data @@ -45,11 +61,20 @@ func (ctx *Context) handleUpdateChan(updates chan *Update) { kbd := bot.Keyboards[screen.InlineKeyboardId] btns := kbd.buttonMap() btn := btns[data] - btn.Action.Act(ctx) + btn.Action.Act(c) } } } +func (c *Context) run(a Action) { + c.available = true + go a.Act(c) +} + +func (c *Context) Available() bool { + return c.available +} + // Changes screen of user to the Id one. func (c *Context) ChangeScreen(screenId ScreenId) error { // Return if it will not change anything. @@ -67,16 +92,55 @@ func (c *Context) ChangeScreen(screenId ScreenId) error { c.Session.ChangeScreen(screenId) c.KeyboardId = screen.KeyboardId + c.available = false + if screen.Action != nil { + c.run(screen.Action) + } + return nil } +// Returns the next update ignoring current screen. +func (c *Context) ReadUpdate() (*Update, error) { + var ( + u *Update + ) + c.ReadingUpdate = true + for { + select { + case u = <-c.updates: + c.ReadingUpdate = false + return u, nil + default: + if !c.available { + return nil, NotAvailableErr + } + } + } + +} + +// Returns the next text message that the user sends. +func (c *Context) ReadTextMessage() (string, error) { + u, err := c.ReadUpdate() + if err != nil { + return "", err + } + if u.Message == nil { + return "", WrongUpdateType{} + } + + return u.Message.Text, nil +} + // Sends to the user specified text. -func (c *Context) Send(text string) error { - msg := apix.NewMessage(c.Id.ToTelegram(), text) +func (c *Context) Send(v ...any) error { + msg := apix.NewMessage(c.Id.ToTelegram(), fmt.Sprint(v...)) _, err := c.B.Send(msg) return err } +// Sends the formatted with fmt.Sprintf message to the user. func (c *Context) Sendf(format string, v ...any) error { return c.Send(fmt.Sprintf(format, v...)) } diff --git a/src/tx/errors.go b/src/tx/errors.go index 60ee41a..6126a68 100644 --- a/src/tx/errors.go +++ b/src/tx/errors.go @@ -2,10 +2,23 @@ package tx import ( "errors" + "fmt" ) +type WrongUpdateType struct { + Type string +} + var ( ScreenNotExistErr = errors.New("screen does not exist") SessionNotExistErr = errors.New("session does not exist") KeyboardNotExistErr = errors.New("keyboard does not exist") + NotAvailableErr = errors.New("the context is not available") ) + +func (wut WrongUpdateType) Error() string { + if wut.Type == "" { + return "wrong update type" + } + return fmt.Sprintf("wrong update type '%s'", wut.Type) +} diff --git a/src/tx/screen.go b/src/tx/screen.go index 243a578..b579f8a 100644 --- a/src/tx/screen.go +++ b/src/tx/screen.go @@ -24,6 +24,9 @@ type Screen struct { // Keyboard to be displayed on the screen. KeyboardId KeyboardId + + // Action called on the reaching the screen. + Action Action } // Map structure for the screens. @@ -52,6 +55,15 @@ func (s *Screen) Keyboard(kbdId KeyboardId) *Screen { return s } +func (s *Screen) WithAction(a Action) *Screen { + s.Action = a + return s +} + +func (s *Screen) ActionFunc(a ActionFunc) *Screen { + return s.WithAction(a) +} + // Rendering the screen text to string to be sent or printed. func (st ScreenText) String() string { return string(st) diff --git a/src/tx/session.go b/src/tx/session.go index 79b5a7d..085cf88 100644 --- a/src/tx/session.go +++ b/src/tx/session.go @@ -19,9 +19,12 @@ type Session struct { CurrentScreenId ScreenId // ID of the previous screen. PreviousScreenId ScreenId - // The currently showed on display keyboard. + // The currently showed on display keyboard inside Action. KeyboardId KeyboardId + // Is true if currently reading the Update. + ReadingUpdate bool + // Custom data for each user. V map[string]any }