Implemented goroutining for each user.

This commit is contained in:
Andrey Parhomenko 2023-07-12 14:06:05 +03:00
parent 6dd954e8ad
commit 36f529df58
8 changed files with 154 additions and 84 deletions

View file

@ -23,34 +23,13 @@ func NewCustomAction(fn func(*Context)) CustomAction {
} }
func (sc ScreenChange) Act(c *Context) { 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) { func (ca CustomAction) Act(c *Context) {
ca(c) 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)
}
*/

View file

@ -2,7 +2,7 @@ package behx
import ( import (
apix "github.com/go-telegram-bot-api/telegram-bot-api/v5" apix "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"log" //"log"
) )
// The wrapper around Telegram API. // The wrapper around Telegram API.
@ -41,8 +41,9 @@ func (bot *Bot) Run() error {
updates := bot.GetUpdatesChan(uc) updates := bot.GetUpdatesChan(uc)
chans := make(map[SessionId] chan *Update)
for u := range updates { for u := range updates {
if u.Message != nil {
// Create new session if the one does not exist // Create new session if the one does not exist
// for this user. // for this user.
sid := SessionId(u.Message.Chat.ID) sid := SessionId(u.Message.Chat.ID)
@ -50,26 +51,29 @@ func (bot *Bot) Run() error {
bot.sessions.Add(sid) bot.sessions.Add(sid)
} }
session := bot.sessions[sid]
ctx := &Context{
B: bot,
S: session,
}
// The "start" command resets the bot // The "start" command resets the bot
// by executing the Start Action. // by executing the Start Action.
if u.Message.IsCommand() { if u.Message.IsCommand() {
cmd := u.Message.Command() cmd := u.Message.Command()
if cmd == "start" { if cmd == "start" {
bot.Start.Act(ctx) // Getting current session and context.
} session := bot.sessions[sid]
continue ctx := &Context{
B: bot,
Session: session,
} }
screen := bot.Screens[session.CurrentScreenId] chn := make(chan *Update)
err := screen.Render(ctx) chans[sid] = chn
if err != nil { // Starting the goroutine for the user.
log.Println("screen rendering:", err) go ctx.handleUpdateChan(chn)
continue
}
}
chn := chans[sid]
chn <- &u
} }
} }

View file

@ -58,6 +58,15 @@ func (btn *Button) ToTelegramInline() apix.InlineKeyboardButton {
return apix.NewInlineKeyboardButtonData(btn.Text, btn.Text) 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 { func NewButtonRow(btns ...*Button) ButtonRow {
return btns return btns

View file

@ -1,27 +1,60 @@
package behx 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 // The type represents way to interact with user in
// handling functions. Is provided to Act() function always. // handling functions. Is provided to Act() function always.
type Context struct { type Context struct {
S *Session *Session
B *Bot 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. // Changes screen of user to the Id one.
func (c *Context) ChangeScreen(screenId ScreenId) error { 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) { if !c.B.ScreenExists(screenId) {
return ScreenNotExistErr return ScreenNotExistErr
} }
c.S.PreviousScreenId = c.S.CurrentScreenId screen := c.B.Screens[screenId]
c.S.CurrentScreenId = screenId screen.Render(c)
c.Session.ChangeScreen(screenId)
c.KeyboardId = screen.KeyboardId
return nil return nil
} }

View file

@ -66,7 +66,7 @@ func (kbd *Keyboard) buttonMap() ButtonMap {
ret := make(ButtonMap) ret := make(ButtonMap)
for _, vi := range kbd.Rows { for _, vi := range kbd.Rows {
for _, vj := range vi { for _, vj := range vi {
ret[vj.Text] = vj ret[vj.Key()] = vj
} }
} }

View file

@ -41,9 +41,9 @@ func (st ScreenText) String() string {
return string(st) 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 { func (s *Screen) Render(c *Context) error {
id := c.S.Id.ToTelegram() id := c.Id.ToTelegram()
msg := apix.NewMessage(id, s.Text.String()) msg := apix.NewMessage(id, s.Text.String())
@ -60,22 +60,28 @@ func (s *Screen) Render(c *Context) error {
return err 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)
// Replace keyboard with the new one.
if s.KeyboardId != "" {
kbd, ok := c.B.Keyboards[s.KeyboardId] kbd, ok := c.B.Keyboards[s.KeyboardId]
if !ok { if !ok {
return KeyboardNotExistErr return KeyboardNotExistErr
} }
tkbd = kbd.ToTelegram()
}
msg.ReplyMarkup = kbd.ToTelegram() msg.ReplyMarkup = tkbd
if _, err := c.B.Send(msg) ; err != nil { if _, err := c.B.Send(msg) ; err != nil {
return err return err
} }
} }
return nil return nil
} }

View file

@ -1,5 +1,11 @@
package behx package behx
import (
apix "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
type Update = apix.Update
// Represents unique value to identify chats. // Represents unique value to identify chats.
// In fact is simply ID of the chat. // In fact is simply ID of the chat.
type SessionId int64 type SessionId int64
@ -7,9 +13,13 @@ type SessionId int64
// The type represents current state of // The type represents current state of
// user interaction per each of them. // user interaction per each of them.
type Session struct { type Session struct {
// Unique identifier for the session, Telegram chat's ID.
Id SessionId Id SessionId
// Current screen identifier.
CurrentScreenId ScreenId CurrentScreenId ScreenId
// ID of the previous screen.
PreviousScreenId ScreenId PreviousScreenId ScreenId
// The currently showed on display keyboard.
KeyboardId KeyboardId KeyboardId KeyboardId
} }
@ -17,13 +27,25 @@ type Session struct {
// as key. // as key.
type SessionMap map[SessionId] *Session 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 { func (si SessionId) ToTelegram() int64 {
return int64(si) return int64(si)
} }
// Add new empty session by it's ID.
func (sm SessionMap) Add(sid SessionId) { func (sm SessionMap) Add(sid SessionId) {
sm[sid] = &Session{ sm[sid] = NewSession(sid)
Id: sid,
} }
}

View file

@ -11,7 +11,7 @@ import (
var rootKbd = behx.NewKeyboard( var rootKbd = behx.NewKeyboard(
behx.NewButtonRow( behx.NewButtonRow(
behx.NewButton( behx.NewButton(
"PRESS ME", "1",
behx.NewCustomAction(func(c *behx.Context){ behx.NewCustomAction(func(c *behx.Context){
log.Println("pressed the button!") log.Println("pressed the button!")
}), }),
@ -21,9 +21,18 @@ var rootKbd = behx.NewKeyboard(
})), })),
), ),
behx.NewButtonRow( behx.NewButtonRow(
behx.NewButton("PRESS ME 3", behx.NewCustomAction(func(c *behx.Context){ behx.NewButton("To second screen", behx.NewScreenChange("second")),
log.Println("pressed third button!") ),
})), )
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", "root",
) )
var secondScreen = behx.NewScreen(
"Second screen!",
"",
"second",
)
var behaviour = behx.NewBehaviour( var behaviour = behx.NewBehaviour(
behx.NewScreenChange("start"), behx.NewScreenChange("start"),
behx.ScreenMap{ behx.ScreenMap{
"start": startScreen, "start": startScreen,
"second": secondScreen,
}, },
behx.KeyboardMap{ behx.KeyboardMap{
"root": rootKbd, "root": rootKbd,
"inline": inlineKbd, "inline": inlineKbd,
"second": secondKbd,
}, },
) )