diff --git a/src/behx/action.go b/src/behx/action.go index 8679774..07d57ff 100644 --- a/src/behx/action.go +++ b/src/behx/action.go @@ -23,34 +23,13 @@ func NewCustomAction(fn func(*Context)) CustomAction { } func (sc ScreenChange) Act(c *Context) { - c.ChangeScreen(ScreenId(sc)) + err := c.ChangeScreen(ScreenId(sc)) + if err != nil { + panic(err) + } } func (ca CustomAction) Act(c *Context) { ca(c) } - -/* -// The type describes interface -// defining what should be done. -type Actioner interface { - Act(*bot) -} - -// Simple way to define that the button should just change -// screen to the Id. -type ScreenChanger ScreenId - -// Custom function to be executed on button press. -type ActionFunc func(*Bot) - -func (a ActionFunc) Act(bot *Bot) { - a(bot) -} - -func (sc ScreenChanger) Act(bot *Bot) { - bot.ChangeScreenTo(sc) -} -*/ - diff --git a/src/behx/bot.go b/src/behx/bot.go index 91244b3..b71ebc0 100644 --- a/src/behx/bot.go +++ b/src/behx/bot.go @@ -2,7 +2,7 @@ package behx import ( apix "github.com/go-telegram-bot-api/telegram-bot-api/v5" - "log" + //"log" ) // The wrapper around Telegram API. @@ -41,36 +41,40 @@ func (bot *Bot) Run() error { updates := bot.GetUpdatesChan(uc) + chans := make(map[SessionId] chan *Update) for u := range updates { - - // Create new session if the one does not exist - // for this user. - sid := SessionId(u.Message.Chat.ID) - if _, ok := bot.sessions[sid] ; !ok { - bot.sessions.Add(sid) - } - - session := bot.sessions[sid] - ctx := &Context{ - B: bot, - S: session, - } - - // The "start" command resets the bot - // by executing the Start Action. - if u.Message.IsCommand() { - cmd := u.Message.Command() - if cmd == "start" { - bot.Start.Act(ctx) + if u.Message != nil { + // Create new session if the one does not exist + // for this user. + sid := SessionId(u.Message.Chat.ID) + if _, ok := bot.sessions[sid] ; !ok { + bot.sessions.Add(sid) + } + + + // The "start" command resets the bot + // by executing the Start Action. + if u.Message.IsCommand() { + cmd := u.Message.Command() + if cmd == "start" { + // Getting current session and context. + session := bot.sessions[sid] + ctx := &Context{ + B: bot, + Session: session, + } + + chn := make(chan *Update) + chans[sid] = chn + // Starting the goroutine for the user. + go ctx.handleUpdateChan(chn) + continue + } } - continue - } - - screen := bot.Screens[session.CurrentScreenId] - err := screen.Render(ctx) - if err != nil { - log.Println("screen rendering:", err) - } + + chn := chans[sid] + chn <- &u + } } return nil diff --git a/src/behx/button.go b/src/behx/button.go index cce3a33..ae96e05 100644 --- a/src/behx/button.go +++ b/src/behx/button.go @@ -58,6 +58,15 @@ func (btn *Button) ToTelegramInline() apix.InlineKeyboardButton { return apix.NewInlineKeyboardButtonData(btn.Text, btn.Text) } +// Return the key of the button to identify it by messages and callbacks. +func (btn *Button) Key() string { + if btn.Data != "" { + return btn.Data + } + + // If no match then return the data one with data the same as the text. + return btn.Text +} func NewButtonRow(btns ...*Button) ButtonRow { return btns diff --git a/src/behx/context.go b/src/behx/context.go index 04daa61..21c7e28 100644 --- a/src/behx/context.go +++ b/src/behx/context.go @@ -1,27 +1,60 @@ package behx -import ( - apix "github.com/go-telegram-bot-api/telegram-bot-api/v5" -) - -type Update = apix.Update - // The type represents way to interact with user in // handling functions. Is provided to Act() function always. type Context struct { - S *Session + *Session B *Bot - U *Update +} + +// Goroutie function to handle each user. +func (ctx *Context) handleUpdateChan(updates chan *Update) { + bot := ctx.B + session := ctx.Session + bot.Start.Act(ctx) + for u := range updates { + if u.Message != nil { + screen := bot.Screens[session.CurrentScreenId] + + kbd := bot.Keyboards[screen.KeyboardId] + btns := kbd.buttonMap() + + text := u.Message.Text + btn, ok := btns[text] + + // Skipping wrong text messages. + if !ok { + continue + } + + btn.Action.Act(ctx) + } else if u.CallbackQuery != nil { + cb := apix.NewCallback(u.CallbackQuery.ID, u.CallbackQuery.Data) + + _, err := bot.Request(cb) + if err != nil { + panic(err) + } + } + } } // Changes screen of user to the Id one. func (c *Context) ChangeScreen(screenId ScreenId) error { + // Return if it will not change anything. + if c.CurrentScreenId == screenId { + return nil + } + if !c.B.ScreenExists(screenId) { return ScreenNotExistErr } - c.S.PreviousScreenId = c.S.CurrentScreenId - c.S.CurrentScreenId = screenId + screen := c.B.Screens[screenId] + screen.Render(c) + + c.Session.ChangeScreen(screenId) + c.KeyboardId = screen.KeyboardId return nil } diff --git a/src/behx/keyboard.go b/src/behx/keyboard.go index 4e58c9e..f81cb66 100644 --- a/src/behx/keyboard.go +++ b/src/behx/keyboard.go @@ -66,7 +66,7 @@ func (kbd *Keyboard) buttonMap() ButtonMap { ret := make(ButtonMap) for _, vi := range kbd.Rows { for _, vj := range vi { - ret[vj.Text] = vj + ret[vj.Key()] = vj } } diff --git a/src/behx/screen.go b/src/behx/screen.go index fc6b90f..cb130f5 100644 --- a/src/behx/screen.go +++ b/src/behx/screen.go @@ -41,9 +41,9 @@ func (st ScreenText) String() string { return string(st) } -// Renders output of the screen to the side of the user. +// Renders output of the screen only to the side of the user. func (s *Screen) Render(c *Context) error { - id := c.S.Id.ToTelegram() + id := c.Id.ToTelegram() msg := apix.NewMessage(id, s.Text.String()) @@ -60,21 +60,27 @@ func (s *Screen) Render(c *Context) error { return err } - if s.KeyboardId != "" { - msg = apix.NewMessage(id, ">") + msg = apix.NewMessage(id, ">") + // Checking if we need to resend the keyboard. + if s.KeyboardId != c.KeyboardId { + // Remove keyboard by default. + var tkbd any + tkbd = apix.NewRemoveKeyboard(true) - kbd, ok := c.B.Keyboards[s.KeyboardId] - if !ok { - return KeyboardNotExistErr + // Replace keyboard with the new one. + if s.KeyboardId != "" { + kbd, ok := c.B.Keyboards[s.KeyboardId] + if !ok { + return KeyboardNotExistErr + } + tkbd = kbd.ToTelegram() } - msg.ReplyMarkup = kbd.ToTelegram() + msg.ReplyMarkup = tkbd if _, err := c.B.Send(msg) ; err != nil { return err } - - } - + } return nil } diff --git a/src/behx/session.go b/src/behx/session.go index b73be5a..3f5c421 100644 --- a/src/behx/session.go +++ b/src/behx/session.go @@ -1,5 +1,11 @@ package behx +import ( + apix "github.com/go-telegram-bot-api/telegram-bot-api/v5" +) + +type Update = apix.Update + // Represents unique value to identify chats. // In fact is simply ID of the chat. type SessionId int64 @@ -7,9 +13,13 @@ type SessionId int64 // The type represents current state of // user interaction per each of them. type Session struct { + // Unique identifier for the session, Telegram chat's ID. Id SessionId + // Current screen identifier. CurrentScreenId ScreenId + // ID of the previous screen. PreviousScreenId ScreenId + // The currently showed on display keyboard. KeyboardId KeyboardId } @@ -17,13 +27,25 @@ type Session struct { // as key. type SessionMap map[SessionId] *Session +// Return new empty session with +func NewSession(id SessionId) *Session { + return &Session{ + Id: id, + } +} + +// Changes screen of user to the Id one for the session. +func (c *Session) ChangeScreen(screenId ScreenId) { + c.PreviousScreenId = c.CurrentScreenId + c.CurrentScreenId = screenId +} + +// Convert the SessionId to Telegram API's type. func (si SessionId) ToTelegram() int64 { return int64(si) } +// Add new empty session by it's ID. func (sm SessionMap) Add(sid SessionId) { - sm[sid] = &Session{ - Id: sid, - } + sm[sid] = NewSession(sid) } - diff --git a/src/cmd/test/main.go b/src/cmd/test/main.go index 56029f0..d3b2b74 100644 --- a/src/cmd/test/main.go +++ b/src/cmd/test/main.go @@ -11,7 +11,7 @@ import ( var rootKbd = behx.NewKeyboard( behx.NewButtonRow( behx.NewButton( - "PRESS ME", + "1", behx.NewCustomAction(func(c *behx.Context){ log.Println("pressed the button!") }), @@ -21,9 +21,18 @@ var rootKbd = behx.NewKeyboard( })), ), behx.NewButtonRow( - behx.NewButton("PRESS ME 3", behx.NewCustomAction(func(c *behx.Context){ - log.Println("pressed third button!") - })), + behx.NewButton("To second screen", behx.NewScreenChange("second")), + ), +) + +var secondKbd = behx.NewKeyboard( + behx.NewButtonRow( + behx.NewButton( + "❤", + behx.NewCustomAction(func(c *behx.Context){ + log.Println("pressed the button!") + }), + ), ), ) @@ -53,14 +62,22 @@ var startScreen = behx.NewScreen( "root", ) +var secondScreen = behx.NewScreen( + "Second screen!", + "", + "second", +) + var behaviour = behx.NewBehaviour( behx.NewScreenChange("start"), behx.ScreenMap{ "start": startScreen, + "second": secondScreen, }, behx.KeyboardMap{ "root": rootKbd, "inline": inlineKbd, + "second": secondKbd, }, )