Re-implemented the customizing by the Widget interface.

This commit is contained in:
Andrey Parhomenko 2023-09-09 07:28:06 +03:00
parent 5b00189ea2
commit b2ce5fe2ea
7 changed files with 400 additions and 293 deletions

View file

@ -16,11 +16,31 @@ type UserData struct {
Counter int Counter int
} }
type MutateMessageWidget struct {
Mutate func(string) string
}
func NewMutateMessageWidget(fn func(string) string) *MutateMessageWidget {
ret := &MutateMessageWidget{}
ret.Mutate = fn
return ret
}
func (w *MutateMessageWidget) Serve(c *tg.Context, updates chan *tg.Update) {
for u := range updates {
if u.Message == nil {
continue
}
text := u.Message.Text
c.Sendf("%s", w.Mutate(text))
}
}
var ( var (
startScreenButton = tg.NewButton("🏠 To the start screen"). startScreenButton = tg.NewButton("🏠 To the start screen").
ScreenChange("start") ScreenChange("start")
incDecKeyboard = tg.NewInline().Row( incDecKeyboard = tg.NewReply().Row(
tg.NewButton("+").ActionFunc(func(c *tg.Context) { tg.NewButton("+").ActionFunc(func(c *tg.Context) {
d := c.Session.Value.(*UserData) d := c.Session.Value.(*UserData)
d.Counter++ d.Counter++
@ -38,12 +58,12 @@ var (
navKeyboard = tg.NewReply(). navKeyboard = tg.NewReply().
WithOneTime(true). WithOneTime(true).
Row( Row(
tg.NewButton("Inc/Dec").ScreenChange("inc/dec"), tg.NewButton("Inc/Dec").ScreenChange("start/inc-dec"),
).Row( ).Row(
tg.NewButton("Upper case").ScreenChange("upper-case"), tg.NewButton("Upper case").ScreenChange("start/upper-case"),
tg.NewButton("Lower case").ScreenChange("lower-case"), tg.NewButton("Lower case").ScreenChange("start/lower-case"),
).Row( ).Row(
tg.NewButton("Send location").ScreenChange("send-location"), tg.NewButton("Send location").ScreenChange("start/send-location"),
) )
sendLocationKeyboard = tg.NewReply(). sendLocationKeyboard = tg.NewReply().
@ -64,7 +84,7 @@ var (
l.Heading, l.Heading,
) )
} else { } else {
_, err = c.Sendf("Somehow wrong location was sent") _, err = c.Sendf("Somehow location was not sent")
} }
if err != nil { if err != nil {
c.Sendf("%q", err) c.Sendf("%q", err)
@ -89,62 +109,69 @@ var beh = tg.NewBehaviour().
WithPreStartFunc(func(c *tg.Context){ WithPreStartFunc(func(c *tg.Context){
c.Sendf("Please, use the /start command to start the bot") c.Sendf("Please, use the /start command to start the bot")
}).WithScreens( }).WithScreens(
tg.NewScreen("start"). tg.NewScreen("start", tg.NewPage(
WithText( "The bot started!",
"The bot started!"+ ).WithInline(
" The bot is supposed to provide basic"+
" understand of how the API works, so just"+
" horse around a bit to guess everything out"+
" by yourself!",
).WithReply(navKeyboard).
// The inline keyboard with link to GitHub page.
WithInline(
tg.NewInline().Row( tg.NewInline().Row(
tg.NewButton("GoT Github page"). tg.NewButton("GoT Github page").
WithUrl("https://github.com/mojosa-software/got"), WithUrl("https://github.com/mojosa-software/got"),
), ),
).WithReply(
navKeyboard,
), ),
),
tg.NewScreen("inc/dec"). tg.NewScreen("start/inc-dec", tg.NewPage(
WithText(
"The screen shows how "+ "The screen shows how "+
"user separated data works "+ "user separated data works "+
"by saving the counter for each of users "+ "by saving the counter for each of users "+
"separately. ", "separately. ",
). ).WithReply(
WithReply(&tg.ReplyKeyboard{Keyboard: incDecKeyboard.Keyboard}). incDecKeyboard,
// The function will be called when reaching the screen. ).ActionFunc(func(c *tg.Context) {
ActionFunc(func(c *tg.Context) { // The function will be calleb before serving page.
d := c.Session.Value.(*UserData) d := c.Session.Value.(*UserData)
c.Sendf("Current counter value = %d", d.Counter) c.Sendf("Current counter value = %d", d.Counter)
}), }),
),
tg.NewScreen("upper-case"). tg.NewScreen("start/upper-case", tg.NewPage(
WithText("Type text and the bot will send you the upper case version to you"). "Type text and the bot will send you the upper case version to you",
WithReply(navToStartKeyboard). ).WithReply(
ActionFunc(mutateMessage(strings.ToUpper)), navToStartKeyboard,
).WithSub(
NewMutateMessageWidget(strings.ToUpper),
),
),
tg.NewScreen("lower-case"). tg.NewScreen("start/lower-case", tg.NewPage(
WithText("Type text and the bot will send you the lower case version"). "Type text and the bot will send you the lower case version",
WithReply(navToStartKeyboard). ).WithReply(
ActionFunc(mutateMessage(strings.ToLower)), navToStartKeyboard,
).WithSub(
NewMutateMessageWidget(strings.ToLower),
),
),
tg.NewScreen("send-location"). tg.NewScreen("start/send-location", tg.NewPage(
WithText("Send your location and I will tell where you are!"). "Send your location and I will tell where you are!",
WithReply(sendLocationKeyboard). ).WithReply(
WithInline( sendLocationKeyboard,
).WithInline(
tg.NewInline().Row( tg.NewInline().Row(
tg.NewButton("Check"). tg.NewButton(
WithData("check"). "Check",
ActionFunc(func(a *tg.Context) { ).WithData(
d := a.Session.Value.(*UserData) "check",
a.Sendf("Counter = %d", d.Counter) ).ActionFunc(func(c *tg.Context) {
d := c.Session.Value.(*UserData)
c.Sendf("Counter = %d", d.Counter)
}), }),
), ),
), ),
),
).WithCommands( ).WithCommands(
tg.NewCommand("start"). tg.NewCommand("start").
Desc("start the bot"). Desc("start or restart the bot or move to the start screen").
ActionFunc(func(c *tg.Context){ ActionFunc(func(c *tg.Context){
c.ChangeScreen("start") c.ChangeScreen("start")
}), }),
@ -156,12 +183,12 @@ var beh = tg.NewBehaviour().
tg.NewCommand("read"). tg.NewCommand("read").
Desc("reads a string and sends it back"). Desc("reads a string and sends it back").
ActionFunc(func(c *tg.Context) { ActionFunc(func(c *tg.Context) {
c.Sendf("Type some text:") /*c.Sendf("Type some text:")
msg, err := c.ReadTextMessage() msg, err := c.ReadTextMessage()
if err != nil { if err != nil {
return return
} }
c.Sendf("You typed %q", msg) c.Sendf("You typed %q", msg)*/
}), }),
tg.NewCommand("image"). tg.NewCommand("image").
Desc("sends a sample image"). Desc("sends a sample image").
@ -177,24 +204,6 @@ var beh = tg.NewBehaviour().
}), }),
) )
func mutateMessage(fn func(string) string) tg.ActionFunc {
return func(c *tg.Context) {
for {
msg, err := c.ReadTextMessage()
if err == tg.NotAvailableErr {
break
} else if err != nil {
panic(err)
}
_, err = c.Sendf("%s", fn(msg))
if err != nil {
panic(err)
}
}
}
}
var gBeh = tg.NewGroupBehaviour(). var gBeh = tg.NewGroupBehaviour().
InitFunc(func(c *tg.GC) { InitFunc(func(c *tg.GC) {
}). }).

View file

@ -187,7 +187,6 @@ func (bot *Bot) handlePrivate(updates chan *Update) {
ctx := &context{ ctx := &context{
Bot: bot, Bot: bot,
Session: session, Session: session,
updates: make(chan *Update),
} }
chn := make(chan *Update) chn := make(chan *Update)
chans[sid] = chn chans[sid] = chn
@ -201,7 +200,6 @@ func (bot *Bot) handlePrivate(updates chan *Update) {
ctx := &context{ ctx := &context{
Bot: bot, Bot: bot,
Session: lsession, Session: lsession,
updates: make(chan *Update),
} }
chn := make(chan *Update) chn := make(chan *Update)
chans[sid] = chn chans[sid] = chn

View file

@ -6,9 +6,13 @@ import (
// The general keyboard type used both in Reply and Inline. // The general keyboard type used both in Reply and Inline.
type Keyboard struct { type Keyboard struct {
// The action is called if there is no
// defined action for the button.
Action *action
Rows []ButtonRow Rows []ButtonRow
} }
// The type represents reply keyboards.
type ReplyKeyboard struct { type ReplyKeyboard struct {
Keyboard Keyboard
// If true will be removed after one press. // If true will be removed after one press.
@ -17,16 +21,18 @@ type ReplyKeyboard struct {
Remove bool Remove bool
} }
// The keyboard to be emdedded into the messages. // The type represents keyboard to be emdedded into the messages.
type InlineKeyboard struct { type InlineKeyboard struct {
Keyboard Keyboard
} }
// Returns new empty inline keyboard.
func NewInline() *InlineKeyboard { func NewInline() *InlineKeyboard {
ret := &InlineKeyboard{} ret := &InlineKeyboard{}
return ret return ret
} }
// Returns new empty reply keyboard.
func NewReply() *ReplyKeyboard { func NewReply() *ReplyKeyboard {
ret := &ReplyKeyboard {} ret := &ReplyKeyboard {}
return ret return ret
@ -41,6 +47,18 @@ func (kbd *InlineKeyboard) Row(btns ...*Button) *InlineKeyboard {
kbd.Rows = append(kbd.Rows, btns) kbd.Rows = append(kbd.Rows, btns)
return kbd return kbd
} }
// Set default action for the buttons in keyboard.
func (kbd *InlineKeyboard) WithAction(a Action) *InlineKeyboard {
kbd.Action = newAction(a)
return kbd
}
// Alias to WithAction to simpler define actions.
func (kbd *InlineKeyboard) ActionFunc(fn ActionFunc) *InlineKeyboard {
return kbd.WithAction(fn)
}
// Adds a new button row to the current keyboard. // Adds a new button row to the current keyboard.
func (kbd *ReplyKeyboard) Row(btns ...*Button) *ReplyKeyboard { func (kbd *ReplyKeyboard) Row(btns ...*Button) *ReplyKeyboard {
// For empty row. We do not need that. // For empty row. We do not need that.
@ -51,6 +69,17 @@ func (kbd *ReplyKeyboard) Row(btns ...*Button) *ReplyKeyboard {
return kbd return kbd
} }
// Set default action for the keyboard.
func (kbd *ReplyKeyboard) WithAction(a Action) *ReplyKeyboard {
kbd.Action = newAction(a)
return kbd
}
// Alias to WithAction for simpler callback declarations.
func (kbd *ReplyKeyboard) ActionFunc(fn ActionFunc) *ReplyKeyboard {
return kbd.WithAction(fn)
}
// Convert the Keyboard to the Telegram API type of reply keyboard. // Convert the Keyboard to the Telegram API type of reply keyboard.
func (kbd *ReplyKeyboard) ToApi() any { func (kbd *ReplyKeyboard) ToApi() any {
// Shades everything. // Shades everything.
@ -74,6 +103,7 @@ func (kbd *ReplyKeyboard) ToApi() any {
return tgbotapi.NewReplyKeyboard(rows...) return tgbotapi.NewReplyKeyboard(rows...)
} }
// Convert the inline keyboard to markup for the tgbotapi.
func (kbd *InlineKeyboard) ToApi() tgbotapi.InlineKeyboardMarkup { func (kbd *InlineKeyboard) ToApi() tgbotapi.InlineKeyboardMarkup {
rows := [][]tgbotapi.InlineKeyboardButton{} rows := [][]tgbotapi.InlineKeyboardButton{}
for _, row := range kbd.Rows { for _, row := range kbd.Rows {
@ -87,18 +117,22 @@ func (kbd *InlineKeyboard) ToApi() tgbotapi.InlineKeyboardMarkup {
return tgbotapi.NewInlineKeyboardMarkup(rows...) return tgbotapi.NewInlineKeyboardMarkup(rows...)
} }
// Set if we should remove current keyboard on the user side
// when sending the keyboard.
func (kbd *ReplyKeyboard) WithRemove(remove bool) *ReplyKeyboard { func (kbd *ReplyKeyboard) WithRemove(remove bool) *ReplyKeyboard {
kbd.Remove = remove kbd.Remove = remove
return kbd return kbd
} }
// Set if the keyboard should be hidden after
// one of buttons is pressede.
func (kbd *ReplyKeyboard) WithOneTime(oneTime bool) *ReplyKeyboard { func (kbd *ReplyKeyboard) WithOneTime(oneTime bool) *ReplyKeyboard {
kbd.OneTime = oneTime kbd.OneTime = oneTime
return kbd return kbd
} }
// Returns the map of buttons. Used to define the Action. // Returns the map of buttons. Used to define the Action.
func (kbd Keyboard) buttonMap() ButtonMap { 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 {
@ -108,3 +142,4 @@ func (kbd Keyboard) buttonMap() ButtonMap {
return ret return ret
} }

View file

@ -3,17 +3,15 @@ package tg
import ( import (
"fmt" "fmt"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" //tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
) )
type context struct { type context struct {
Session *Session Session *Session
// To reach the bot abilities inside callbacks.
Bot *Bot Bot *Bot
updates chan *Update widgetUpdates chan *Update
// Is true if currently reading the Update. CurScreen, PrevScreen *Screen
readingUpdate bool
curScreen, prevScreen *Screen
} }
// The type represents way to interact with user in // The type represents way to interact with user in
@ -29,102 +27,46 @@ func (c *context) handleUpdateChan(updates chan *Update) {
c.run(beh.Init, nil) c.run(beh.Init, nil)
} }
for u := range updates { for u := range updates {
var act Action
screen := c.curScreen
// The part is added to implement custom update handling. // The part is added to implement custom update handling.
if u.Message != nil { if !session.started {
if !session.Started {
if u.Message.IsCommand() && if u.Message.IsCommand() &&
u.Message.Command() == "start" { u.Message.Command() == "start" {
// Special treatment for the "/start" // Special treatment for the "/start"
// command. // command.
session.Started = true session.started = true
cmdName := CommandName("start") cmdName := CommandName("start")
cmd, ok := beh.Commands[cmdName] cmd, ok := beh.Commands[cmdName]
if ok { if ok {
act = cmd.Action if cmd.Action != nil {
c.run(cmd.Action, u)
}
} else { } else {
// Some usage. // Some usage.
} }
} else { } else {
// Prestart handling. // Prestart handling.
act = preStart c.run(preStart, u)
} }
} else if u.Message.IsCommand() {
continue
}
if u.Message != nil && u.Message.IsCommand() {
// Command handling. // Command handling.
cmdName := CommandName(u.Message.Command()) cmdName := CommandName(u.Message.Command())
cmd, ok := beh.Commands[cmdName] cmd, ok := beh.Commands[cmdName]
if ok { if ok {
act = cmd.Action if cmd.Action != nil {
c.run(cmd.Action, u)
}
} else { } else {
// Some usage. // Some usage.
} }
} else {
// Simple messages handling.
kbd := screen.Reply
if kbd == nil {
if c.readingUpdate {
c.updates <- u
}
continue
}
btns := kbd.buttonMap()
text := u.Message.Text
btn, ok := btns[text]
if !ok {
if u.Message.Location != nil {
for _, b := range btns {
if b.SendLocation {
btn = b
ok = true
}
}
} else if c.readingUpdate {
// Skipping the update sending it to
// the reading goroutine.
c.updates <- u
continue
}
}
if ok {
act = btn.Action
}
}
} else if u.CallbackQuery != nil && session.Started {
cb := tgbotapi.NewCallback(
u.CallbackQuery.ID,
u.CallbackQuery.Data,
)
data := u.CallbackQuery.Data
_, err := c.Bot.Api.Request(cb)
if err != nil {
panic(err)
}
kbd := screen.Inline
if kbd == nil {
if c.readingUpdate {
c.updates <- u
}
continue continue
} }
btns := kbd.buttonMap() // The standard thing - send messages to widgets.
btn, ok := btns[data] c.widgetUpdates <- u
if !ok && c.readingUpdate {
c.updates <- u
continue
}
if !ok {
c.Sendf("%q", btns)
continue
}
act = btn.Action
}
if act != nil {
c.run(act, u)
}
} }
} }
@ -135,29 +77,8 @@ func (c *context) run(a Action, u *Update) {
}) })
} }
// Returns the next update ignoring current screen. func (c *context) Render(v Renderable) ([]*Message, error) {
func (c *context) ReadUpdate() (*Update, error) { return c.Bot.Render(c.Session.Id, v)
c.readingUpdate = true
u := <-c.updates
c.readingUpdate = false
if u == nil {
return nil, NotAvailableErr
}
return u, nil
}
// Returns the next text message that the user sends.
func (c *context) ReadTextMessage() (string, error) {
u, err := c.ReadUpdate()
if err != nil {
return "", err
}
if u.Message == nil {
return "", WrongUpdateType{}
}
return u.Message.Text, nil
} }
// Sends to the Sendable object. // Sends to the Sendable object.
@ -217,19 +138,36 @@ func (c *Context) ChangeScreen(screenId ScreenId) error {
// Stop the reading by sending the nil, // Stop the reading by sending the nil,
// since we change the screen and // since we change the screen and
// current goroutine needs to be stopped. // current goroutine needs to be stopped.
if c.readingUpdate { // if c.readingUpdate {
c.updates <- nil // c.Updates <- nil
} // }
// Getting the screen and changing to // Getting the screen and changing to
// then executing its action. // then executing its widget.
screen := c.Bot.behaviour.Screens[screenId] screen := c.Bot.behaviour.Screens[screenId]
c.prevScreen = c.curScreen c.PrevScreen = c.CurScreen
c.curScreen = screen c.CurScreen = screen
c.Bot.Render(c.Session.Id, screen)
if screen.Action != nil { // Making the new channel for the widget.
c.run(screen.Action, c.Update) if c.widgetUpdates != nil {
close(c.widgetUpdates)
} }
c.widgetUpdates = make(chan *Update)
if screen.Widget != nil {
// Running the widget if the screen has one.
go screen.Widget.Serve(c, c.widgetUpdates)
} else {
// Skipping updates if there is no
// widget to handle them.
go func() {
for _ = range c.widgetUpdates {}
}()
}
//c.Bot.Render(c.Session.Id, screen)
//if screen.Action != nil {
//c.run(screen.Action, c.Update)
//}
return nil return nil
} }

View file

@ -1,9 +1,5 @@
package tg package tg
import (
tgbotapi "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
@ -11,100 +7,19 @@ type ScreenId string
// Mostly what buttons to show. // Mostly what buttons to show.
type Screen struct { type Screen struct {
Id ScreenId Id ScreenId
// The text to be displayed when the screen is // The widget to run when reaching the screen.
// reached. Widget Widget
Text string
// The keyboard to be sent in the message part.
Inline *InlineKeyboard
// Keyboard to be displayed on the screen.
Reply *ReplyKeyboard
// Action called on the reaching the screen.
Action *action
} }
// Map structure for the screens. // Map structure for the screens.
type ScreenMap map[ScreenId]*Screen type ScreenMap map[ScreenId]*Screen
// Returns the new screen with specified Text and Keyboard. // Returns the new screen with specified name and widget.
func NewScreen(id ScreenId) *Screen { func NewScreen(id ScreenId, widget Widget) *Screen {
return &Screen{ return &Screen{
Id: id, Id: id,
Widget: widget,
} }
} }
// Returns the screen with specified text printing on appearing.
func (s *Screen) WithText(text string) *Screen {
s.Text = text
return s
}
func (s *Screen) WithInline(ikbd *InlineKeyboard) *Screen {
s.Inline= ikbd
return s
}
func (s *Screen) WithReply(kbd *ReplyKeyboard) *Screen {
s.Reply = kbd
return s
}
func (s *Screen) WithAction(a Action) *Screen {
s.Action = newAction(a)
return s
}
func (s *Screen) ActionFunc(a ActionFunc) *Screen {
return s.WithAction(a)
}
func (s *Screen) Render(
sid SessionId, bot *Bot,
) ([]*SendConfig, error) {
cid := sid.ToApi()
reply := s.Reply
inline := s.Inline
ret := []*SendConfig{}
var txt string
// Screen text and inline keyboard.
if s.Text != "" {
txt = s.Text
} else if inline != nil {
// Default to send the keyboard.
txt = ">"
}
if txt != "" {
msgConfig := tgbotapi.NewMessage(cid, txt)
if inline != nil {
msgConfig.ReplyMarkup = inline.ToApi()
} else if reply != nil {
msgConfig.ReplyMarkup = reply.ToApi()
ret = append(ret, &SendConfig{Message: &msgConfig})
return ret, nil
} else {
msgConfig.ReplyMarkup = NewReply().
WithRemove(true).
ToApi()
ret = append(ret, &SendConfig{Message: &msgConfig})
return ret, nil
}
ret = append(ret, &SendConfig{Message: &msgConfig})
}
// Screen text and reply keyboard.
if reply != nil {
msgConfig := tgbotapi.NewMessage(cid, ">")
msgConfig.ReplyMarkup = reply.ToApi()
ret = append(ret, &SendConfig{
Message: &msgConfig,
})
} else {
// Removing keyboard if there is none.
msgConfig := tgbotapi.NewMessage(cid, ">")
msgConfig.ReplyMarkup = NewReply().
WithRemove(true).
ToApi()
ret = append(ret, &SendConfig{Message: &msgConfig})
}
return ret, nil
}

View file

@ -16,7 +16,7 @@ type Session struct {
Id SessionId Id SessionId
// True if the session started. // True if the session started.
// (got the '/start' command. // (got the '/start' command.
Started bool started bool
// Custom value for each user. // Custom value for each user.
Value any Value any
} }

212
tg/widget.go Normal file
View file

@ -0,0 +1,212 @@
package tg
import (
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
// Implementing the interface provides
// ability to build your own widgets,
// aka components.
type Widget interface {
// When the update channel is closed
// widget MUST end its work.
// Mostly made by looping over the
// updates range.
Serve(*Context, chan *Update)
}
// The basic widget to provide keyboard functionality
// without implementing much.
type Page struct {
Text string
SubWidget Widget
Inline *InlineKeyboard
Reply *ReplyKeyboard
Action Action
}
// Return new page with the specified text.
func NewPage(text string) *Page {
ret := &Page{}
ret.Text = text
return ret
}
// Set the inline keyboard.
func (p *Page) WithInline(inline *InlineKeyboard) *Page {
p.Inline = inline
return p
}
// Set the reply keyboard.
func (p *Page) WithReply(reply *ReplyKeyboard) *Page {
p.Reply = reply
return p
}
// Set the action to be run before serving.
func (p *Page) WithAction(a Action) *Page {
p.Action = a
return p
}
// Alias to with action to simpler define actions.
func (p *Page) ActionFunc(fn ActionFunc) *Page {
return p.WithAction(fn)
}
// Set the sub widget that will get the skipped
// updates.
func (p *Page) WithSub(sub Widget) *Page {
p.SubWidget = sub
return p
}
func (p *Page) Serve(
c *Context, updates chan *Update,
) {
msgs, err := c.Render(p)
if err != nil {
panic(err)
}
// The inline message is always returned
// and the reply one is useless in our case.
inlineMsg := msgs[0]
if p.Action != nil {
c.run(p.Action, c.Update)
}
var subUpdates chan *Update
if p.SubWidget != nil {
subUpdates = make(chan *Update)
go p.SubWidget.Serve(c, subUpdates)
defer close(subUpdates)
}
for u := range updates {
var act Action
if u.Message != nil {
text := u.Message.Text
kbd := p.Reply
if kbd == nil {
if subUpdates != nil {
subUpdates <- u
}
continue
}
btns := kbd.ButtonMap()
btn, ok := btns[text]
if !ok {
if u.Message.Location != nil {
for _, b := range btns {
if b.SendLocation {
btn = b
ok = true
}
}
} else if subUpdates != nil {
subUpdates <- u
}
}
if btn != nil {
act = btn.Action
} else if kbd.Action != nil {
act = kbd.Action
}
} else if u.CallbackQuery != nil {
if u.CallbackQuery.Message.MessageID != inlineMsg.MessageID {
if subUpdates != nil {
subUpdates <- u
}
continue
}
cb := tgbotapi.NewCallback(
u.CallbackQuery.ID,
u.CallbackQuery.Data,
)
data := u.CallbackQuery.Data
_, err := c.Bot.Api.Request(cb)
if err != nil {
panic(err)
}
kbd := p.Inline
if kbd == nil {
if subUpdates != nil {
subUpdates <- u
}
continue
}
btns := kbd.ButtonMap()
btn, ok := btns[data]
if !ok {
if subUpdates != nil {
subUpdates <- u
}
continue
}
if btn != nil {
act = btn.Action
} else if kbd.Action != nil {
act = kbd.Action
}
}
if act != nil {
c.run(act, u)
}
}
}
func (s *Page) Render(
sid SessionId, bot *Bot,
) ([]*SendConfig, error) {
cid := sid.ToApi()
reply := s.Reply
inline := s.Inline
ret := []*SendConfig{}
var txt string
// Screen text and inline keyboard.
if s.Text != "" {
txt = s.Text
} else if inline != nil {
// Default to send the keyboard.
txt = ">"
}
if txt != "" {
msgConfig := tgbotapi.NewMessage(cid, txt)
if inline != nil {
msgConfig.ReplyMarkup = inline.ToApi()
} else if reply != nil {
msgConfig.ReplyMarkup = reply.ToApi()
ret = append(ret, &SendConfig{Message: &msgConfig})
return ret, nil
} else {
msgConfig.ReplyMarkup = NewReply().
WithRemove(true).
ToApi()
ret = append(ret, &SendConfig{Message: &msgConfig})
return ret, nil
}
ret = append(ret, &SendConfig{Message: &msgConfig})
}
// Screen text and reply keyboard.
if reply != nil {
msgConfig := tgbotapi.NewMessage(cid, ">")
msgConfig.ReplyMarkup = reply.ToApi()
ret = append(ret, &SendConfig{
Message: &msgConfig,
})
} else {
// Removing keyboard if there is none.
msgConfig := tgbotapi.NewMessage(cid, ">")
msgConfig.ReplyMarkup = NewReply().
WithRemove(true).
ToApi()
ret = append(ret, &SendConfig{Message: &msgConfig})
}
return ret, nil
}