Implemented basic keyboard and screen rendering.
This commit is contained in:
parent
564f1e8dc4
commit
c537ccdec1
12 changed files with 105 additions and 102 deletions
5
go.mod
5
go.mod
|
@ -2,4 +2,7 @@ module boteval
|
||||||
|
|
||||||
go 1.20
|
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
|
||||||
|
)
|
||||||
|
|
2
go.sum
2
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 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
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=
|
||||||
|
|
|
@ -3,11 +3,11 @@ package behx
|
||||||
// Implementing the intereface lets you
|
// Implementing the intereface lets you
|
||||||
// provide behaviour for the buttons etc.
|
// provide behaviour for the buttons etc.
|
||||||
type Action interface {
|
type Action interface {
|
||||||
Act(*Bot)
|
Act(*Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Customized action for the bot.
|
// Customized action for the bot.
|
||||||
type CustomAction func(*Bot)
|
type CustomAction func(*Context)
|
||||||
|
|
||||||
// The type implements changing screen to the underlying ScreenId
|
// The type implements changing screen to the underlying ScreenId
|
||||||
type ScreenChange ScreenId
|
type ScreenChange ScreenId
|
||||||
|
@ -18,7 +18,7 @@ func NewScreenChange(screen string) ScreenChange {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns new CustomAction.
|
// Returns new CustomAction.
|
||||||
func NewCustomAction(fn func(*Bot)) CustomAction {
|
func NewCustomAction(fn func(*Context)) CustomAction {
|
||||||
return CustomAction(fn)
|
return CustomAction(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ func (sc ScreenChange) Act(c *Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ca CustomAction) Act(c *Context) {
|
func (ca CustomAction) Act(c *Context) {
|
||||||
ca(bot)
|
ca(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ type Behaviour struct {
|
||||||
|
|
||||||
// Check whether the screen exists in the behaviour.
|
// Check whether the screen exists in the behaviour.
|
||||||
func (beh *Behaviour) ScreenExists(id ScreenId) bool {
|
func (beh *Behaviour) ScreenExists(id ScreenId) bool {
|
||||||
_, ok := bot.behaviour.Screens[id]
|
_, ok := beh.Screens[id]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ type Bot struct {
|
||||||
|
|
||||||
// Return the new bot for running the Behaviour.
|
// Return the new bot for running the Behaviour.
|
||||||
func NewBot(token string, beh *Behaviour, sessions SessionMap) (*Bot, error) {
|
func NewBot(token string, beh *Behaviour, sessions SessionMap) (*Bot, error) {
|
||||||
bot, err := tgbotapi.NewBotAPI(token)
|
bot, err := apix.NewBotAPI(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ func NewBot(token string, beh *Behaviour, sessions SessionMap) (*Bot, error) {
|
||||||
return &Bot{
|
return &Bot{
|
||||||
BotAPI: bot,
|
BotAPI: bot,
|
||||||
Behaviour: beh,
|
Behaviour: beh,
|
||||||
|
sessions: make(SessionMap),
|
||||||
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -34,34 +35,38 @@ func NewBot(token string, beh *Behaviour, sessions SessionMap) (*Bot, error) {
|
||||||
func (bot *Bot) Run() error {
|
func (bot *Bot) Run() error {
|
||||||
bot.Debug = true
|
bot.Debug = true
|
||||||
|
|
||||||
uc := tgbotapi.NewUpdate(0)
|
uc := apix.NewUpdate(0)
|
||||||
uc.Timeout = 60
|
uc.Timeout = 60
|
||||||
|
|
||||||
updates := bot.GetUpdatesChan(uc)
|
updates := bot.GetUpdatesChan(uc)
|
||||||
|
|
||||||
for u := range updates {
|
for u := range updates {
|
||||||
|
|
||||||
// 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)
|
||||||
if !bot.sessions.Exist(sid) {
|
if _, ok := bot.sessions[sid] ; !ok {
|
||||||
bot.sessions.Add(sid)
|
bot.sessions.Add(sid)
|
||||||
}
|
}
|
||||||
|
|
||||||
session := bot.sessions.Get(sid)
|
session := bot.sessions[sid]
|
||||||
ctx := &beh.Context{
|
ctx := &Context{
|
||||||
B: bot,
|
B: bot,
|
||||||
S: session,
|
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.MessageIsCommand() {
|
if u.Message.IsCommand() {
|
||||||
cmd := u.Message.Command()
|
cmd := u.Message.Command()
|
||||||
if cmd == "start" {
|
if cmd == "start" {
|
||||||
bot.Start.Act(ctx)
|
bot.Start.Act(ctx)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
screen := bot.Screens[session.CurrentScreenId]
|
||||||
|
screen.Render(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
// The type wraps Telegram API's button to provide Action functionality.
|
// The type wraps Telegram API's button to provide Action functionality.
|
||||||
type Button struct {
|
type Button struct {
|
||||||
apix.KeyboardButton
|
apix.KeyboardButton
|
||||||
Action Acter
|
Action Action
|
||||||
}
|
}
|
||||||
|
|
||||||
type ButtonMap map[string] *Button
|
type ButtonMap map[string] *Button
|
||||||
|
@ -18,7 +18,7 @@ type ButtonRow []*Button
|
||||||
// Returns new button with specified text and action.
|
// Returns new button with specified text and action.
|
||||||
func NewButton(text string, action Action) *Button {
|
func NewButton(text string, action Action) *Button {
|
||||||
return &Button{
|
return &Button{
|
||||||
KeyboardButton: apix.NewKeyboardButton(text)
|
KeyboardButton: apix.NewKeyboardButton(text),
|
||||||
Action: action,
|
Action: action,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,32 @@
|
||||||
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
|
S *Session
|
||||||
B *Bot
|
B *Bot
|
||||||
|
U *Update
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) ChangeScreen(screen ScreenId) error {
|
// Changes screen of user to the Id one.
|
||||||
if !bot.ScreenExists(screenId) {
|
func (c *Context) ChangeScreen(screenId ScreenId) error {
|
||||||
|
if !c.B.ScreenExists(screenId) {
|
||||||
return ScreenNotExistErr
|
return ScreenNotExistErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.S.PreviousScreenId = c.S.CurrentScreenId
|
||||||
|
c.S.CurrentScreenId = screenId
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sends to the user specified text.
|
||||||
func (c *Context) Send(text string) error {
|
func (c *Context) Send(text string) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -20,29 +20,39 @@ var otherKeyboard = tgbotapi.NewReplyKeyboard(
|
||||||
// The type represents reply keyboard which
|
// The type represents reply keyboard which
|
||||||
// is supposed to be showed on a Screen.
|
// is supposed to be showed on a Screen.
|
||||||
type Keyboard struct {
|
type Keyboard struct {
|
||||||
rows []ButtonRow
|
Rows []ButtonRow
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the new reply keyboard with rows as specified.
|
// Return the new reply keyboard with rows as specified.
|
||||||
func NewKeyboard(rows ...ButtonRow) *Keyboard {
|
func NewKeyboard(rows ...ButtonRow) *Keyboard {
|
||||||
return &Keyboard{
|
return &Keyboard{
|
||||||
rows: rows,
|
Rows: rows,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the Keyboard to the Telegram API type.
|
// Convert the Keyboard to the Telegram API type.
|
||||||
func (kbd *Keyboard) ToTelegram() ReplyKeyboardMarkup {
|
func (kbd *Keyboard) ToTelegram() apix.ReplyKeyboardMarkup {
|
||||||
rows := [][]apix.KeyboardButton{}
|
rows := [][]apix.KeyboardButton{}
|
||||||
for _, row := range kbd.rows {
|
for _, row := range kbd.Rows {
|
||||||
buttons := []apix.KeyboardButton{}
|
buttons := []apix.KeyboardButton{}
|
||||||
for _, button := range row {
|
for _, button := range row {
|
||||||
buttons = append(buttons, apix.NewKeyboardButton(button.text))
|
buttons = append(buttons, apix.NewKeyboardButton(button.Text))
|
||||||
}
|
}
|
||||||
rows = append(rows, buttons)
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,9 @@
|
||||||
package behx
|
package behx
|
||||||
|
|
||||||
|
import (
|
||||||
|
apix "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
|
)
|
||||||
|
|
||||||
// Unique identifier for the screen.
|
// Unique identifier for the screen.
|
||||||
type ScreenId string
|
type ScreenId string
|
||||||
|
|
||||||
|
@ -14,11 +18,12 @@ type Screen struct {
|
||||||
Text ScreenText
|
Text ScreenText
|
||||||
// Keyboard to be displayed on the screen.
|
// Keyboard to be displayed on the screen.
|
||||||
Keyboard *Keyboard
|
Keyboard *Keyboard
|
||||||
|
// The keyboard to be sent in the message part.
|
||||||
|
InlineKeyboard *Keyboard
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map structure for the screens.
|
// Map structure for the screens.
|
||||||
type ScreenMap Map[ScreenId, *Screen]
|
type ScreenMap map[ScreenId] *Screen
|
||||||
map[ScreenId] *Screen
|
|
||||||
|
|
||||||
// Returns the new screen with specified Text and Keyboard.
|
// Returns the new screen with specified Text and Keyboard.
|
||||||
func NewScreen(text ScreenText, kbd *Keyboard) *Screen {
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,4 +16,13 @@ type Session struct {
|
||||||
// as key.
|
// as key.
|
||||||
type SessionMap map[SessionId] *Session
|
type SessionMap map[SessionId] *Session
|
||||||
|
|
||||||
|
func (si SessionId) ToTelegram() int64 {
|
||||||
|
return int64(si)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sm SessionMap) Add(sid SessionId) {
|
||||||
|
sm[sid] = &Session{
|
||||||
|
Id: sid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,12 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"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"
|
"boteval/src/behx"
|
||||||
)
|
)
|
||||||
|
|
||||||
var startScreen = behx.NewScreen(
|
var startScreen = behx.NewScreen(
|
||||||
|
"Hello, World!",
|
||||||
behx.NewKeyboard(
|
behx.NewKeyboard(
|
||||||
behx.NewButtonRow(
|
behx.NewButtonRow(
|
||||||
behx.NewButton(
|
behx.NewButton(
|
||||||
|
@ -29,43 +30,17 @@ var startScreen = behx.NewScreen(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
var behaviour = behx.Behaviour{
|
var behaviour = &behx.Behaviour{
|
||||||
StartAction: behx.NewScreenChange("start"),
|
Start: behx.NewScreenChange("start"),
|
||||||
Screens: behx.ScreenMap{
|
Screens: behx.ScreenMap{
|
||||||
"start": startScreen,
|
"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() {
|
func main() {
|
||||||
token := os.Getenv("BOT_TOKEN")
|
token := os.Getenv("BOT_TOKEN")
|
||||||
|
|
||||||
bot, err := tgbotapi.NewBotAPI(token)
|
bot, err := behx.NewBot(token, behaviour, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
|
@ -73,31 +48,6 @@ func main() {
|
||||||
bot.Debug = true
|
bot.Debug = true
|
||||||
|
|
||||||
log.Printf("Authorized on account %s", bot.Self.UserName)
|
log.Printf("Authorized on account %s", bot.Self.UserName)
|
||||||
|
bot.Run()
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue