From c537ccdec1676f86aa552b979721f14e48d607ca Mon Sep 17 00:00:00 2001 From: surdeus Date: Wed, 12 Jul 2023 00:33:51 +0300 Subject: [PATCH] Implemented basic keyboard and screen rendering. --- go.mod | 5 +++- go.sum | 2 ++ src/behx/action.go | 8 +++--- src/behx/beh.go | 2 +- src/behx/bot.go | 17 +++++++----- src/behx/button.go | 4 +-- src/behx/context.go | 16 +++++++++-- src/behx/keyboard.go | 24 ++++++++++++----- src/behx/map.go | 20 -------------- src/behx/screen.go | 36 +++++++++++++++++++++++-- src/behx/session.go | 9 +++++++ src/cmd/test/main.go | 64 +++++--------------------------------------- 12 files changed, 105 insertions(+), 102 deletions(-) delete mode 100644 src/behx/map.go diff --git a/go.mod b/go.mod index cd8348f..7fbaf75 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module boteval go 1.20 -require github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 // indirect +require ( + github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 // indirect + github.com/mojosa-software/godat v0.0.0-20230711170316-a335bad31575 // indirect +) diff --git a/go.sum b/go.sum index db8e45c..087e333 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8= +github.com/mojosa-software/godat v0.0.0-20230711170316-a335bad31575 h1:wtaYzLbEND7JSY7L+huFU4IDT4nbuDolKRfnQ1d6KTc= +github.com/mojosa-software/godat v0.0.0-20230711170316-a335bad31575/go.mod h1:E6ohOj8PpUJBQOSRdrLygjoO+Te6yfeox3ZtaItsHUg= diff --git a/src/behx/action.go b/src/behx/action.go index 7f77b77..8679774 100644 --- a/src/behx/action.go +++ b/src/behx/action.go @@ -3,11 +3,11 @@ package behx // Implementing the intereface lets you // provide behaviour for the buttons etc. type Action interface { - Act(*Bot) + Act(*Context) } // Customized action for the bot. -type CustomAction func(*Bot) +type CustomAction func(*Context) // The type implements changing screen to the underlying ScreenId type ScreenChange ScreenId @@ -18,7 +18,7 @@ func NewScreenChange(screen string) ScreenChange { } // Returns new CustomAction. -func NewCustomAction(fn func(*Bot)) CustomAction { +func NewCustomAction(fn func(*Context)) CustomAction { return CustomAction(fn) } @@ -27,7 +27,7 @@ func (sc ScreenChange) Act(c *Context) { } func (ca CustomAction) Act(c *Context) { - ca(bot) + ca(c) } diff --git a/src/behx/beh.go b/src/behx/beh.go index ee42c06..16da3b8 100644 --- a/src/behx/beh.go +++ b/src/behx/beh.go @@ -13,7 +13,7 @@ type Behaviour struct { // Check whether the screen exists in the behaviour. func (beh *Behaviour) ScreenExists(id ScreenId) bool { - _, ok := bot.behaviour.Screens[id] + _, ok := beh.Screens[id] return ok } diff --git a/src/behx/bot.go b/src/behx/bot.go index ca3d517..b6f79d9 100644 --- a/src/behx/bot.go +++ b/src/behx/bot.go @@ -13,7 +13,7 @@ type Bot struct { // Return the new bot for running the Behaviour. func NewBot(token string, beh *Behaviour, sessions SessionMap) (*Bot, error) { - bot, err := tgbotapi.NewBotAPI(token) + bot, err := apix.NewBotAPI(token) if err != nil { return nil, err } @@ -26,6 +26,7 @@ func NewBot(token string, beh *Behaviour, sessions SessionMap) (*Bot, error) { return &Bot{ BotAPI: bot, Behaviour: beh, + sessions: make(SessionMap), }, nil } @@ -34,34 +35,38 @@ func NewBot(token string, beh *Behaviour, sessions SessionMap) (*Bot, error) { func (bot *Bot) Run() error { bot.Debug = true - uc := tgbotapi.NewUpdate(0) + uc := apix.NewUpdate(0) uc.Timeout = 60 updates := bot.GetUpdatesChan(uc) for u := range updates { + // Create new session if the one does not exist // for this user. sid := SessionId(u.Message.Chat.ID) - if !bot.sessions.Exist(sid) { + if _, ok := bot.sessions[sid] ; !ok { bot.sessions.Add(sid) } - session := bot.sessions.Get(sid) - ctx := &beh.Context{ + session := bot.sessions[sid] + ctx := &Context{ B: bot, S: session, } // The "start" command resets the bot // by executing the Start Action. - if u.MessageIsCommand() { + if u.Message.IsCommand() { cmd := u.Message.Command() if cmd == "start" { bot.Start.Act(ctx) } continue } + + screen := bot.Screens[session.CurrentScreenId] + screen.Render(ctx) } return nil diff --git a/src/behx/button.go b/src/behx/button.go index 56d3717..e60389a 100644 --- a/src/behx/button.go +++ b/src/behx/button.go @@ -7,7 +7,7 @@ import ( // The type wraps Telegram API's button to provide Action functionality. type Button struct { apix.KeyboardButton - Action Acter + Action Action } type ButtonMap map[string] *Button @@ -18,7 +18,7 @@ type ButtonRow []*Button // Returns new button with specified text and action. func NewButton(text string, action Action) *Button { return &Button{ - KeyboardButton: apix.NewKeyboardButton(text) + KeyboardButton: apix.NewKeyboardButton(text), Action: action, } } diff --git a/src/behx/context.go b/src/behx/context.go index 27f71b8..7c836b4 100644 --- a/src/behx/context.go +++ b/src/behx/context.go @@ -1,20 +1,32 @@ 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 B *Bot + U *Update } -func (c *Context) ChangeScreen(screen ScreenId) error { - if !bot.ScreenExists(screenId) { +// Changes screen of user to the Id one. +func (c *Context) ChangeScreen(screenId ScreenId) error { + if !c.B.ScreenExists(screenId) { return ScreenNotExistErr } + c.S.PreviousScreenId = c.S.CurrentScreenId + c.S.CurrentScreenId = screenId + return nil } +// Sends to the user specified text. func (c *Context) Send(text string) error { return nil diff --git a/src/behx/keyboard.go b/src/behx/keyboard.go index bc7b7a6..f528d27 100644 --- a/src/behx/keyboard.go +++ b/src/behx/keyboard.go @@ -20,29 +20,39 @@ var otherKeyboard = tgbotapi.NewReplyKeyboard( // The type represents reply keyboard which // is supposed to be showed on a Screen. type Keyboard struct { - rows []ButtonRow + Rows []ButtonRow } // Return the new reply keyboard with rows as specified. func NewKeyboard(rows ...ButtonRow) *Keyboard { return &Keyboard{ - rows: rows, + Rows: rows, } } // Convert the Keyboard to the Telegram API type. -func (kbd *Keyboard) ToTelegram() ReplyKeyboardMarkup { +func (kbd *Keyboard) ToTelegram() apix.ReplyKeyboardMarkup { rows := [][]apix.KeyboardButton{} - for _, row := range kbd.rows { + for _, row := range kbd.Rows { buttons := []apix.KeyboardButton{} for _, button := range row { - buttons = append(buttons, apix.NewKeyboardButton(button.text)) + buttons = append(buttons, apix.NewKeyboardButton(button.Text)) } rows = append(rows, buttons) } - return apix.NewReplyKeyboard(rows) + return apix.NewReplyKeyboard(rows...) } -func (kbd *Keyboard) ButtonMap() ButtonMap +// Returns the map of buttons. Used to define the Action. +func (kbd *Keyboard) buttonMap() ButtonMap { + ret := make(ButtonMap) + for _, vi := range kbd.Rows { + for _, vj := range vi { + ret[vj.Text] = vj + } + } + + return ret +} diff --git a/src/behx/map.go b/src/behx/map.go deleted file mode 100644 index f698e2f..0000000 --- a/src/behx/map.go +++ /dev/null @@ -1,20 +0,0 @@ -package behx - -type Map[K comparable, V any] map[K] V - -func (m Map[K, V]) Exist(k K) bool { - _, ok := sm[sid] - return ok -} - -func (m Map[K, V]) Get(k K, v V) V { - ret := m[sid] - return ret -} - - -func (sm Map[K, V]) Set(v V) { - sm[sid] = &Session{ - Id: sid, - } -} diff --git a/src/behx/screen.go b/src/behx/screen.go index 6d7c36d..8bd8f7e 100644 --- a/src/behx/screen.go +++ b/src/behx/screen.go @@ -1,5 +1,9 @@ package behx +import ( + apix "github.com/go-telegram-bot-api/telegram-bot-api/v5" +) + // Unique identifier for the screen. type ScreenId string @@ -14,11 +18,12 @@ type Screen struct { Text ScreenText // Keyboard to be displayed on the screen. Keyboard *Keyboard + // The keyboard to be sent in the message part. + InlineKeyboard *Keyboard } // Map structure for the screens. -type ScreenMap Map[ScreenId, *Screen] -map[ScreenId] *Screen +type ScreenMap map[ScreenId] *Screen // Returns the new screen with specified Text and Keyboard. func NewScreen(text ScreenText, kbd *Keyboard) *Screen { @@ -28,4 +33,31 @@ func NewScreen(text ScreenText, kbd *Keyboard) *Screen { } } +// Rendering the screen text to string to be sent or printed. +func (st ScreenText) String() string { + return string(st) +} + +// Renders output of the screen to the side of the user. +func (s *Screen) Render(c *Context) { + id := c.S.Id.ToTelegram() + msg := apix.NewMessage(id, s.Text.String()) + + // First sending the inline keyboard. + if s.InlineKeyboard != nil { + msg.ReplyMarkup = s.InlineKeyboard.ToTelegram() + if _, err := c.B.Send(msg) ; err != nil { + panic(err) + } + } + + // Then sending the screen one. + if s.Keyboard != nil { + msg = apix.NewMessage(id, "check") + msg.ReplyMarkup = s.Keyboard.ToTelegram() + if _, err := c.B.Send(msg) ; err != nil { + panic(err) + } + } +} diff --git a/src/behx/session.go b/src/behx/session.go index ae88372..11d8090 100644 --- a/src/behx/session.go +++ b/src/behx/session.go @@ -16,4 +16,13 @@ type Session struct { // as key. type SessionMap map[SessionId] *Session +func (si SessionId) ToTelegram() int64 { + return int64(si) +} + +func (sm SessionMap) Add(sid SessionId) { + sm[sid] = &Session{ + Id: sid, + } +} diff --git a/src/cmd/test/main.go b/src/cmd/test/main.go index ca0cd09..ea36673 100644 --- a/src/cmd/test/main.go +++ b/src/cmd/test/main.go @@ -4,11 +4,12 @@ import ( "log" "os" - tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" - "boteval/src/cmd/behx" + //tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" + "boteval/src/behx" ) var startScreen = behx.NewScreen( + "Hello, World!", behx.NewKeyboard( behx.NewButtonRow( behx.NewButton( @@ -29,43 +30,17 @@ var startScreen = behx.NewScreen( ), ) -var behaviour = behx.Behaviour{ - StartAction: behx.NewScreenChange("start"), +var behaviour = &behx.Behaviour{ + Start: behx.NewScreenChange("start"), Screens: behx.ScreenMap{ "start": startScreen, }, } -var numericKeyboard = tgbotapi.NewReplyKeyboard( - tgbotapi.NewKeyboardButtonRow( - tgbotapi.NewKeyboardButton("1"), - tgbotapi.NewKeyboardButton("2"), - tgbotapi.NewKeyboardButton("3"), - ), - tgbotapi.NewKeyboardButtonRow( - tgbotapi.NewKeyboardButton("4"), - tgbotapi.NewKeyboardButton("5"), - tgbotapi.NewKeyboardButton("6"), - ), -) - -var otherKeyboard = tgbotapi.NewReplyKeyboard( - tgbotapi.NewKeyboardButtonRow( - tgbotapi.NewKeyboardButton("a"), - tgbotapi.NewKeyboardButton("b"), - tgbotapi.NewKeyboardButton("c"), - ), - tgbotapi.NewKeyboardButtonRow( - tgbotapi.NewKeyboardButton("d"), - tgbotapi.NewKeyboardButton("e"), - tgbotapi.NewKeyboardButton("f"), - ), -) - func main() { token := os.Getenv("BOT_TOKEN") - bot, err := tgbotapi.NewBotAPI(token) + bot, err := behx.NewBot(token, behaviour, nil) if err != nil { log.Panic(err) } @@ -73,31 +48,6 @@ func main() { bot.Debug = true log.Printf("Authorized on account %s", bot.Self.UserName) - - u := tgbotapi.NewUpdate(0) - u.Timeout = 60 - - updates := bot.GetUpdatesChan(u) - - for update := range updates { - if update.Message == nil { // ignore non-Message updates - continue - } - - msg := tgbotapi.NewMessage(update.Message.Chat.ID, update.Message.Text) - - switch update.Message.Text { - case "open": - msg.ReplyMarkup = numericKeyboard - case "letters" : - msg.ReplyMarkup = otherKeyboard - case "close": - msg.ReplyMarkup = tgbotapi.NewRemoveKeyboard(true) - } - - if _, err := bot.Send(msg); err != nil { - log.Panic(err) - } - } + bot.Run() }