This commit is contained in:
Andrey Parhomenko 2023-07-09 01:28:59 +03:00
parent d7db586637
commit 564f1e8dc4
12 changed files with 383 additions and 2 deletions

56
src/behx/action.go Normal file
View file

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

29
src/behx/beh.go Normal file
View file

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

69
src/behx/bot.go Normal file
View file

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

29
src/behx/button.go Normal file
View file

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

22
src/behx/context.go Normal file
View file

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

11
src/behx/errors.go Normal file
View file

@ -0,0 +1,11 @@
package behx
import (
"errors"
)
var (
ScreenNotExistErr = errors.New("screen does not exist")
SessionNotExistErr = errors.New("session does not exist")
)

48
src/behx/keyboard.go Normal file
View file

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

5
src/behx/main.go Normal file
View file

@ -0,0 +1,5 @@
package behx
// The package implements behaviourial
// definition for the Telegram bots through the API.

20
src/behx/map.go Normal file
View file

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

31
src/behx/screen.go Normal file
View file

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

19
src/behx/session.go Normal file
View file

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

View file

@ -1,4 +1,3 @@
package main package main
import ( import (
@ -6,9 +5,37 @@ import (
"os" "os"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" 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( var numericKeyboard = tgbotapi.NewReplyKeyboard(
tgbotapi.NewKeyboardButtonRow( tgbotapi.NewKeyboardButtonRow(
tgbotapi.NewKeyboardButton("1"), 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() { func main() {
token := os.Getenv("BOT_TOKEN") token := os.Getenv("BOT_TOKEN")
@ -49,6 +89,8 @@ func main() {
switch update.Message.Text { switch update.Message.Text {
case "open": case "open":
msg.ReplyMarkup = numericKeyboard msg.ReplyMarkup = numericKeyboard
case "letters" :
msg.ReplyMarkup = otherKeyboard
case "close": case "close":
msg.ReplyMarkup = tgbotapi.NewRemoveKeyboard(true) msg.ReplyMarkup = tgbotapi.NewRemoveKeyboard(true)
} }