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) {
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)
}
*/

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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,
},
)