diff --git a/src/behx/action.go b/src/behx/action.go new file mode 100644 index 0000000..7f77b77 --- /dev/null +++ b/src/behx/action.go @@ -0,0 +1,56 @@ +package behx + +// Implementing the intereface lets you +// provide behaviour for the buttons etc. +type Action interface { + Act(*Bot) +} + +// Customized action for the bot. +type CustomAction func(*Bot) + +// The type implements changing screen to the underlying ScreenId +type ScreenChange ScreenId + +// Returns new ScreenChange. +func NewScreenChange(screen string) ScreenChange { + return ScreenChange(screen) +} + +// Returns new CustomAction. +func NewCustomAction(fn func(*Bot)) CustomAction { + return CustomAction(fn) +} + +func (sc ScreenChange) Act(c *Context) { + c.ChangeScreen(ScreenId(sc)) +} + +func (ca CustomAction) Act(c *Context) { + ca(bot) +} + + +/* +// 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/beh.go b/src/behx/beh.go new file mode 100644 index 0000000..ee42c06 --- /dev/null +++ b/src/behx/beh.go @@ -0,0 +1,29 @@ +package behx + +// The package implements +// behaviour for the Telegram bots. + + +// The type describes behaviour for the bot. +type Behaviour struct { + Start Action + Screens ScreenMap +} + + +// Check whether the screen exists in the behaviour. +func (beh *Behaviour) ScreenExists(id ScreenId) bool { + _, ok := bot.behaviour.Screens[id] + return ok +} + +// Returns the screen by it's ID. +func (beh *Behaviour) GetScreen(id ScreenId) *Screen { + if !beh.ScreenExists(id) { + panic(ScreenNotExistErr) + } + + screen := beh.Screens[id] + return screen +} + diff --git a/src/behx/bot.go b/src/behx/bot.go new file mode 100644 index 0000000..ca3d517 --- /dev/null +++ b/src/behx/bot.go @@ -0,0 +1,69 @@ +package behx + +import ( + apix "github.com/go-telegram-bot-api/telegram-bot-api/v5" +) + +// The wrapper around Telegram API. +type Bot struct { + *apix.BotAPI + *Behaviour + sessions SessionMap +} + +// Return the new bot for running the Behaviour. +func NewBot(token string, beh *Behaviour, sessions SessionMap) (*Bot, error) { + bot, err := tgbotapi.NewBotAPI(token) + if err != nil { + return nil, err + } + + // Make new sessions if no current are provided. + if sessions == nil { + sessions = make(SessionMap) + } + + return &Bot{ + BotAPI: bot, + Behaviour: beh, + + }, nil +} + +// Run the bot with the Behaviour. +func (bot *Bot) Run() error { + bot.Debug = true + + uc := tgbotapi.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) { + bot.sessions.Add(sid) + } + + session := bot.sessions.Get(sid) + ctx := &beh.Context{ + B: bot, + S: session, + } + + // The "start" command resets the bot + // by executing the Start Action. + if u.MessageIsCommand() { + cmd := u.Message.Command() + if cmd == "start" { + bot.Start.Act(ctx) + } + continue + } + } + + return nil +} + diff --git a/src/behx/button.go b/src/behx/button.go new file mode 100644 index 0000000..56d3717 --- /dev/null +++ b/src/behx/button.go @@ -0,0 +1,29 @@ +package behx + +import ( + apix "github.com/go-telegram-bot-api/telegram-bot-api/v5" +) + +// The type wraps Telegram API's button to provide Action functionality. +type Button struct { + apix.KeyboardButton + Action Acter +} + +type ButtonMap map[string] *Button + +// Represents the reply button row. +type ButtonRow []*Button + +// Returns new button with specified text and action. +func NewButton(text string, action Action) *Button { + return &Button{ + KeyboardButton: apix.NewKeyboardButton(text) + Action: action, + } +} + +func NewButtonRow(btns ...*Button) ButtonRow { + return btns +} + diff --git a/src/behx/context.go b/src/behx/context.go new file mode 100644 index 0000000..27f71b8 --- /dev/null +++ b/src/behx/context.go @@ -0,0 +1,22 @@ +package behx + +// The type represents way to interact with user in +// handling functions. Is provided to Act() function always. +type Context struct { + S *Session + B *Bot +} + +func (c *Context) ChangeScreen(screen ScreenId) error { + if !bot.ScreenExists(screenId) { + return ScreenNotExistErr + } + + return nil +} + +func (c *Context) Send(text string) error { + + return nil +} + diff --git a/src/behx/errors.go b/src/behx/errors.go new file mode 100644 index 0000000..d06cf04 --- /dev/null +++ b/src/behx/errors.go @@ -0,0 +1,11 @@ +package behx + +import ( + "errors" +) + +var ( + ScreenNotExistErr = errors.New("screen does not exist") + SessionNotExistErr = errors.New("session does not exist") +) + diff --git a/src/behx/keyboard.go b/src/behx/keyboard.go new file mode 100644 index 0000000..bc7b7a6 --- /dev/null +++ b/src/behx/keyboard.go @@ -0,0 +1,48 @@ +package behx + +import ( + apix "github.com/go-telegram-bot-api/telegram-bot-api/v5" +) +/* +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"), + ), +)*/ + +// The type represents reply keyboard which +// is supposed to be showed on a Screen. +type Keyboard struct { + rows []ButtonRow +} + +// Return the new reply keyboard with rows as specified. +func NewKeyboard(rows ...ButtonRow) *Keyboard { + return &Keyboard{ + rows: rows, + } +} + +// Convert the Keyboard to the Telegram API type. +func (kbd *Keyboard) ToTelegram() ReplyKeyboardMarkup { + rows := [][]apix.KeyboardButton{} + for _, row := range kbd.rows { + buttons := []apix.KeyboardButton{} + for _, button := range row { + buttons = append(buttons, apix.NewKeyboardButton(button.text)) + } + rows = append(rows, buttons) + } + + return apix.NewReplyKeyboard(rows) +} + +func (kbd *Keyboard) ButtonMap() ButtonMap + diff --git a/src/behx/main.go b/src/behx/main.go new file mode 100644 index 0000000..2a51af6 --- /dev/null +++ b/src/behx/main.go @@ -0,0 +1,5 @@ +package behx + +// The package implements behaviourial +// definition for the Telegram bots through the API. + diff --git a/src/behx/map.go b/src/behx/map.go new file mode 100644 index 0000000..f698e2f --- /dev/null +++ b/src/behx/map.go @@ -0,0 +1,20 @@ +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 new file mode 100644 index 0000000..6d7c36d --- /dev/null +++ b/src/behx/screen.go @@ -0,0 +1,31 @@ +package behx + +// Unique identifier for the screen. +type ScreenId string + +// Should be replaced with something that can be +// dinamicaly rendered. (WIP) +type ScreenText string + +// Screen statement of the bot. +// Mostly what buttons to show. +type Screen struct { + // Text to be sent to the user when changing to the screen. + Text ScreenText + // Keyboard to be displayed on the screen. + Keyboard *Keyboard +} + +// Map structure for the screens. +type ScreenMap Map[ScreenId, *Screen] +map[ScreenId] *Screen + +// Returns the new screen with specified Text and Keyboard. +func NewScreen(text ScreenText, kbd *Keyboard) *Screen { + return &Screen { + Text: text, + Keyboard: kbd, + } +} + + diff --git a/src/behx/session.go b/src/behx/session.go new file mode 100644 index 0000000..ae88372 --- /dev/null +++ b/src/behx/session.go @@ -0,0 +1,19 @@ +package behx + +// Represents unique value to identify chats. +// In fact is simply ID of the chat. +type SessionId int64 + +// The type represents current state of +// user interaction per each of them. +type Session struct { + Id SessionId + CurrentScreenId ScreenId + PreviousScreenId ScreenId +} + +// The type represents map of sessions using +// as key. +type SessionMap map[SessionId] *Session + + diff --git a/src/cmd/test/main.go b/src/cmd/test/main.go index 2416681..ca0cd09 100644 --- a/src/cmd/test/main.go +++ b/src/cmd/test/main.go @@ -1,4 +1,3 @@ - package main import ( @@ -6,9 +5,37 @@ import ( "os" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" - + "boteval/src/cmd/behx" ) +var startScreen = behx.NewScreen( + behx.NewKeyboard( + behx.NewButtonRow( + behx.NewButton( + "PRESS ME", + behx.NewCustomAction(func(c *behx.Context){ + log.Println("pressed the button!") + }), + ), + behx.NewButton("PRESS ME 2", behx.NewCustomAction(func(c *behx.Context){ + log.Println("pressed another button!") + })), + ), + behx.NewButtonRow( + behx.NewButton("PRESS ME 3", behx.NewCustomAction(func(c *behx.Context){ + log.Println("pressed third button!") + })), + ), + ), +) + +var behaviour = behx.Behaviour{ + StartAction: behx.NewScreenChange("start"), + Screens: behx.ScreenMap{ + "start": startScreen, + }, +} + var numericKeyboard = tgbotapi.NewReplyKeyboard( tgbotapi.NewKeyboardButtonRow( tgbotapi.NewKeyboardButton("1"), @@ -22,6 +49,19 @@ var numericKeyboard = tgbotapi.NewReplyKeyboard( ), ) +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") @@ -49,6 +89,8 @@ func main() { switch update.Message.Text { case "open": msg.ReplyMarkup = numericKeyboard + case "letters" : + msg.ReplyMarkup = otherKeyboard case "close": msg.ReplyMarkup = tgbotapi.NewRemoveKeyboard(true) }