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"+ tg.NewInline().Row(
" understand of how the API works, so just"+ tg.NewButton("GoT Github page").
" horse around a bit to guess everything out"+ WithUrl("https://github.com/mojosa-software/got"),
" by yourself!", ),
).WithReply(navKeyboard). ).WithReply(
// The inline keyboard with link to GitHub page. navKeyboard,
WithInline( ),
tg.NewInline().Row( ),
tg.NewButton("GoT Github page"). tg.NewScreen("start/inc-dec", tg.NewPage(
WithUrl("https://github.com/mojosa-software/got"), "The screen shows how "+
"user separated data works "+
"by saving the counter for each of users "+
"separately. ",
).WithReply(
incDecKeyboard,
).ActionFunc(func(c *tg.Context) {
// The function will be calleb before serving page.
d := c.Session.Value.(*UserData)
c.Sendf("Current counter value = %d", d.Counter)
}),
),
tg.NewScreen("start/upper-case", tg.NewPage(
"Type text and the bot will send you the upper case version to you",
).WithReply(
navToStartKeyboard,
).WithSub(
NewMutateMessageWidget(strings.ToUpper),
), ),
), ),
tg.NewScreen("inc/dec"). tg.NewScreen("start/lower-case", tg.NewPage(
WithText( "Type text and the bot will send you the lower case version",
"The screen shows how "+ ).WithReply(
"user separated data works "+ navToStartKeyboard,
"by saving the counter for each of users "+ ).WithSub(
"separately. ", NewMutateMessageWidget(strings.ToLower),
). ),
WithReply(&tg.ReplyKeyboard{Keyboard: incDecKeyboard.Keyboard}). ),
// The function will be called when reaching the screen.
ActionFunc(func(c *tg.Context) {
d := c.Session.Value.(*UserData)
c.Sendf("Current counter value = %d", d.Counter)
}),
tg.NewScreen("upper-case"). tg.NewScreen("start/send-location", tg.NewPage(
WithText("Type text and the bot will send you the upper case version to you"). "Send your location and I will tell where you are!",
WithReply(navToStartKeyboard). ).WithReply(
ActionFunc(mutateMessage(strings.ToUpper)), sendLocationKeyboard,
).WithInline(
tg.NewScreen("lower-case"). tg.NewInline().Row(
WithText("Type text and the bot will send you the lower case version"). tg.NewButton(
WithReply(navToStartKeyboard). "Check",
ActionFunc(mutateMessage(strings.ToLower)), ).WithData(
"check",
tg.NewScreen("send-location"). ).ActionFunc(func(c *tg.Context) {
WithText("Send your location and I will tell where you are!"). d := c.Session.Value.(*UserData)
WithReply(sendLocationKeyboard). c.Sendf("Counter = %d", d.Counter)
WithInline(
tg.NewInline().Row(
tg.NewButton("Check").
WithData("check").
ActionFunc(func(a *tg.Context) {
d := a.Session.Value.(*UserData)
a.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]
if ok {
act = cmd.Action
} else {
// Some usage.
}
} else {
// Prestart handling.
act = preStart
}
} else if u.Message.IsCommand() {
// Command handling.
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 { } else {
// Simple messages handling. // Prestart handling.
kbd := screen.Reply c.run(preStart, u)
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
} }
btns := kbd.buttonMap() continue
btn, ok := btns[data]
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)
} }
if u.Message != nil && u.Message.IsCommand() {
// Command handling.
cmdName := CommandName(u.Message.Command())
cmd, ok := beh.Commands[cmdName]
if ok {
if cmd.Action != nil {
c.run(cmd.Action, u)
}
} else {
// Some usage.
}
continue
}
// The standard thing - send messages to widgets.
c.widgetUpdates <- 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
}