Small fixes to use more intuitive MessageCompo instead of the Compo method for Inline and Reply keyboards.

This commit is contained in:
Andrey Parhomenko 2023-09-29 13:36:37 +03:00
parent 227dc816fa
commit 47b7c59469
10 changed files with 178 additions and 186 deletions

View file

@ -20,13 +20,11 @@ type SessionData struct {
} }
type MutateMessageWidget struct { type MutateMessageWidget struct {
*tg.Compo
Mutate func(string) string Mutate func(string) string
} }
func NewMutateMessageWidget(fn func(string) string) *MutateMessageWidget { func NewMutateMessageWidget(fn func(string) string) *MutateMessageWidget {
ret := &MutateMessageWidget{} ret := &MutateMessageWidget{}
ret.Compo = tg.NewCompo()
ret.Mutate = fn ret.Mutate = fn
return ret return ret
} }
@ -104,53 +102,55 @@ WithInitFunc(func(c *tg.Context) {
}).WithRootNode(tg.NewRootNode( }).WithRootNode(tg.NewRootNode(
// The "/" widget. // The "/" widget.
tg.RenderFunc(func(c *tg.Context) tg.UI {return tg.UI { tg.RenderFunc(func(c *tg.Context) tg.UI {return tg.UI {
tg.NewMessage(fmt.Sprintf(
tg.NewKeyboard().Row( fmt.Sprint(
"Hello, %s!\n",
"The testing bot started!\n",
"You can see the basics of usage in the ",
"cmd/test/main.go file!",
),
c.SentFrom().UserName,
)).Inline(
tg.NewKeyboard().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"),
).Inline().Compo( ).Inline(),
fmt.Sprintf( ),
fmt.Sprint(
"Hello, %s!\n",
"The testing bot started!\n",
"You can see the basics of usage in the ",
"cmd/test/main.go file!",
),
c.SentFrom().UserName,
),
),
tg.NewKeyboard().Row(
tg.NewButton("Inc/Dec").Go("/inc-dec"),
).Row(
tg.NewButton("Mutate messages").Go("/mutate-messages"),
).Row(
tg.NewButton("Send location").Go("/send-location"),
).Reply().Compo(
"Choose the point of your interest",
),
tg.NewMessage("Choose your interest").Reply(
tg.NewKeyboard().Row(
tg.NewButton("Inc/Dec").Go("/inc-dec"),
).Row(
tg.NewButton("Mutate messages").Go("/mutate-messages"),
).Row(
tg.NewButton("Send location").Go("/send-location"),
).Reply(),
),
}}), }}),
tg.NewNode( tg.NewNode(
"mutate-messages", tg.RenderFunc(func(c *tg.Context) tg.UI { "mutate-messages", tg.RenderFunc(func(c *tg.Context) tg.UI {
return tg.UI{ return tg.UI{
tg.NewKeyboard().Row( tg.NewMessage(
tg.NewButton("Upper case").Go("upper-case"),
tg.NewButton("Lower case").Go("lower-case"),
).Row(
backButton,
).Reply().Compo(
"Choose the function to mutate string", "Choose the function to mutate string",
).Reply(
tg.NewKeyboard().Row(
tg.NewButton("Upper case").Go("upper-case"),
tg.NewButton("Lower case").Go("lower-case"),
).Row(
backButton,
).Reply(),
), ),
} }
}), }),
tg.NewNode( tg.NewNode(
"upper-case", tg.RenderFunc(func(c *tg.Context) tg.UI { "upper-case", tg.RenderFunc(func(c *tg.Context) tg.UI {
return tg.UI{ return tg.UI{
backKeyboard.Reply().Compo( tg.NewMessage(
"Type a string and the bot will convert it to upper case", "Type a string and the bot will convert it to upper case",
), ).Reply(
backKeyboard.Reply(),
),
NewMutateMessageWidget(strings.ToUpper), NewMutateMessageWidget(strings.ToUpper),
} }
}), }),
@ -158,10 +158,11 @@ WithInitFunc(func(c *tg.Context) {
tg.NewNode( tg.NewNode(
"lower-case", tg.RenderFunc(func(c *tg.Context) tg.UI { "lower-case", tg.RenderFunc(func(c *tg.Context) tg.UI {
return tg.UI{ return tg.UI{
backKeyboard.Reply(). tg.NewMessage(
Compo( "Type a string and the bot will convert it to lower case",
"Type a string and the bot will convert it to lower case", ).Reply(
), backKeyboard.Reply(),
),
NewMutateMessageWidget(strings.ToLower), NewMutateMessageWidget(strings.ToLower),
} }
}), }),
@ -172,10 +173,12 @@ WithInitFunc(func(c *tg.Context) {
"inc-dec", tg.RenderFunc(func(c *tg.Context) tg.UI { "inc-dec", tg.RenderFunc(func(c *tg.Context) tg.UI {
d := ExtractSessionData(c) d := ExtractSessionData(c)
return tg.UI{ return tg.UI{
incDecKeyboard.Reply().Compo(fmt.Sprintf( tg.NewMessage(fmt.Sprintf(
"Press the buttons to increment and decrement.\n" + "Press the buttons to increment and decrement.\n" +
"Current counter value = %d", d.Counter, "Current counter value = %d", d.Counter,
)), )).Reply(
incDecKeyboard.Reply(),
),
} }
}), }),
), ),
@ -183,19 +186,25 @@ WithInitFunc(func(c *tg.Context) {
tg.NewNode( tg.NewNode(
"send-location", tg.RenderFunc(func(c *tg.Context) tg.UI { "send-location", tg.RenderFunc(func(c *tg.Context) tg.UI {
return tg.UI { return tg.UI {
tg.NewMessage(
"Press the button to display your counter",
).Inline(
tg.NewKeyboard().Row( tg.NewKeyboard().Row(
tg.NewButton( tg.NewButton(
"Check", "Check",
).WithData( ).WithData(
"check", "check",
).WithAction(tg.Func(func(c *tg.Context) { ).WithAction(tg.Func(func(c *tg.Context) {
d := ExtractSessionData(c) d := ExtractSessionData(c)
c.Sendf("Counter = %d", d.Counter) c.Sendf("Counter = %d", d.Counter)
})), })),
).Inline().Compo("Press the button to display your counter"), ).Inline(),
),
sendLocationKeyboard.Compo( tg.NewMessage(
"Press the button to send your location!", "Press the button to send your location!",
).Reply(
sendLocationKeyboard,
), ),
} }
}), }),
@ -243,19 +252,6 @@ WithPreStart(tg.Func(func(c *tg.Context){
})), })),
)) ))
var gBeh = tg.NewGroupBehaviour().
InitFunc(func(c *tg.GC) {
}).
WithCommands(
tg.NewGroupCommand("hello").ActionFunc(func(c *tg.GC) {
c.Sendf("Hello, World!")
}),
tg.NewGroupCommand("mycounter").ActionFunc(func(c *tg.GC) {
d := c.Session().Data.(*SessionData)
c.Sendf("Your counter value is %d", d.Counter)
}),
)
func main() { func main() {
log.Println(beh.Screens) log.Println(beh.Screens)
token := os.Getenv("BOT_TOKEN") token := os.Getenv("BOT_TOKEN")

View file

@ -49,7 +49,8 @@ func (bot *Bot) Debug(debug bool) *Bot {
} }
// Send the Renderable to the specified session client side. // Send the Renderable to the specified session client side.
// Can be used for both group and private sessions. // Can be used for both group and private sessions because
// SessionId represents both for chat IDs.
func (bot *Bot) Send( func (bot *Bot) Send(
sid SessionId, v Sendable, args ...any, sid SessionId, v Sendable, args ...any,
) (*Message, error) { ) (*Message, error) {
@ -61,17 +62,7 @@ func (bot *Bot) Send(
c := &Context{ c := &Context{
context: ctx, context: ctx,
} }
return c.Bot.Send(c.Session.Id, v)
config := v.SendConfig(c.WithArg(c.MakeArg(args)))
if config.Error != nil {
return nil, config.Error
}
msg, err := bot.Api.Send(config.ToApi())
if err != nil {
return nil, err
}
return &msg, nil
} }
/*func (bot *Bot) Render( /*func (bot *Bot) Render(

View file

@ -107,14 +107,17 @@ type CommandCompo struct {
} }
// Returns new empty CommandCompo. // Returns new empty CommandCompo.
func NewCommandCompo() *CommandCompo { func NewCommandCompo(cmds ...*Command) *CommandCompo {
ret := &CommandCompo{} ret := (&CommandCompo{}).WithCommands(cmds...)
ret.Commands = make(CommandMap) //ret.Commands = make(CommandMap)
return ret return ret
} }
// Set the commands to handle. // Set the commands to handle.
func (w *CommandCompo) WithCommands(cmds ...*Command) *CommandCompo { func (w *CommandCompo) WithCommands(cmds ...*Command) *CommandCompo {
if w.Commands == nil {
w.Commands = make(CommandMap)
}
for _, cmd := range cmds { for _, cmd := range cmds {
if cmd.Name == "" { if cmd.Name == "" {
panic("empty command name") panic("empty command name")

View file

@ -29,19 +29,3 @@ type Component interface {
Server Server
} }
// The type to embed into potential components.
// Implements empty versions of interfaces.
type Compo struct{
*Message
}
func NewCompo() *Compo {
return &Compo{}
}
// Defalut setting message
func (compo *Compo) SetMessage(msg *Message) { compo.Message = msg }
func (compo *Compo) GetMessage() *Message { return compo.Message }
// Default non filtering filter. Always returns false.
func (compo *Compo) Filter(_ *Update) bool {return false}

View file

@ -81,18 +81,30 @@ func (c *Context) Skip(u *Update) {
// Sends to the Sendable object. // Sends to the Sendable object.
func (c *Context) Send(v Sendable) (*Message, error) { func (c *Context) Send(v Sendable) (*Message, error) {
return c.Bot.Send(c.Session.Id, v) config := v.SendConfig(c)
if config.Error != nil {
return nil, config.Error
}
msg, err := c.Bot.Api.Send(config.ToApi())
if err != nil {
return nil, err
}
return &msg, nil
} }
// Sends the formatted with fmt.Sprintf message to the user. // Sends the formatted with fmt.Sprintf message to the user
// using default Markdown parsing format.
func (c *Context) Sendf(format string, v ...any) (*Message, error) { func (c *Context) Sendf(format string, v ...any) (*Message, error) {
return c.Send(NewMessage(fmt.Sprintf(format, v...))) return c.Send(NewMessage(fmt.Sprintf(format, v...)))
} }
// Same as Sendf but uses Markdown 2 format for parsing.
func (c *Context) Sendf2(format string, v ...any) (*Message, error) { func (c *Context) Sendf2(format string, v ...any) (*Message, error) {
return c.Send(NewMessage(fmt.Sprintf(format, v...)).MD2()) return c.Send(NewMessage(fmt.Sprintf(format, v...)).MD2())
} }
// Same as Sendf but uses HTML format for parsing.
func (c *Context) SendfHTML(format string, v ...any) (*Message, error) { func (c *Context) SendfHTML(format string, v ...any) (*Message, error) {
return c.Send(NewMessage(fmt.Sprintf(format, v...)).HTML()) return c.Send(NewMessage(fmt.Sprintf(format, v...)).HTML())
} }
@ -227,7 +239,7 @@ func (c *Context) RunWidget(widget Widget, args ...any) *UpdateChan {
pth := c.Path() pth := c.Path()
compos := widget.Render(c.WithArg(c.MakeArg(args))) compos := widget.Render(c.WithArg(c.MakeArg(args)))
// Leave if changed path. // Leave if changed path.
if pth != c.Path() { if compos == nil || pth != c.Path() {
return nil return nil
} }
chns := make([]*UpdateChan, len(compos)) chns := make([]*UpdateChan, len(compos))
@ -237,13 +249,27 @@ func (c *Context) RunWidget(widget Widget, args ...any) *UpdateChan {
ret := NewUpdateChan() ret := NewUpdateChan()
go func() { go func() {
ln := len(compos)
UPDATE:
for u := range ret.Chan() { for u := range ret.Chan() {
if u == nil {
break
}
cnt := 0
for i, compo := range compos { for i, compo := range compos {
chn := chns[i] chn := chns[i]
if chn.Closed() {
cnt++
continue
}
if !compo.Filter(u) { if !compo.Filter(u) {
chn.Send(u) chn.Send(u)
continue UPDATE
} }
} }
if cnt == ln {
break
}
} }
ret.Close() ret.Close()
for _, chn := range chns { for _, chn := range chns {

View file

@ -23,17 +23,18 @@ var (
) )
type File struct { type File struct {
*Compo *MessageCompo
path string path string
typ FileType typ FileType
caption string caption string
} }
func NewFile(path string) *File { func NewFile(path string) *File {
ret := &File{ ret := &File{}
path: path,
} ret.MessageCompo = NewMessage("")
ret.Compo = NewCompo() ret.path = path
return ret return ret
} }

View file

@ -23,49 +23,41 @@ func (kbd *Inline) ToApi() tgbotapi.InlineKeyboardMarkup {
return tgbotapi.NewInlineKeyboardMarkup(rows...) return tgbotapi.NewInlineKeyboardMarkup(rows...)
} }
// Transform the keyboard to widget with the specified text.
func (kbd *Inline) Compo(text string) *InlineCompo {
ret := &InlineCompo{}
ret.Inline = kbd
ret.Text = text
ret.Compo = NewCompo()
return ret
}
// The type implements message with an inline keyboard. // The type implements message with an inline keyboard.
type InlineCompo struct { type InlineCompo struct {
*Compo *MessageCompo
Text string
*Inline *Inline
} }
// Implementing the Sendable interface. // Implementing the Sendable interface.
func (widget *InlineCompo) SendConfig( func (compo *InlineCompo) SendConfig(
c *Context, c *Context,
) (*SendConfig) { ) (*SendConfig) {
var text string
if widget.Text != "" { sendConfig := compo.MessageCompo.SendConfig(c)
text = widget.Text sendConfig.Message.ReplyMarkup = compo.Inline.ToApi()
} else {
text = ">" return sendConfig
}
// Implementing the Filterer interface.
func (compo *InlineCompo) Filter(u *Update) bool {
if compo == nil || u.CallbackQuery == nil {
return true
} }
sid := c.Session.Id.ToApi() if u.CallbackQuery.Message.MessageID !=
msgConfig := tgbotapi.NewMessage(sid, text) compo.Message.MessageID {
msgConfig.ReplyMarkup = widget.ToApi() return true
}
ret := &SendConfig{} return false
ret.Message = &msgConfig
return ret
} }
// Implementing the Server interface. // Implementing the Server interface.
func (widget *InlineCompo) Serve(c *Context) { func (widget *InlineCompo) Serve(c *Context) {
for u := range c.Input() { for u := range c.Input() {
var act Action var act Action
if u.CallbackQuery == nil {
continue
}
cb := tgbotapi.NewCallback( cb := tgbotapi.NewCallback(
u.CallbackQuery.ID, u.CallbackQuery.ID,
u.CallbackQuery.Data, u.CallbackQuery.Data,
@ -92,17 +84,4 @@ func (widget *InlineCompo) Serve(c *Context) {
} }
} }
// Implementing the Filterer interface.
func (compo *InlineCompo) Filter(u *Update) bool {
if compo == nil || u.CallbackQuery == nil {
return true
}
if u.CallbackQuery.Message.MessageID !=
compo.Message.MessageID {
return true
}
return false
}

View file

@ -5,45 +5,87 @@ import (
) )
// Simple text message type. // Simple text message type.
type MessageConfig struct { type MessageCompo struct {
Compo Message *Message
ParseMode string ParseMode string
Text string Text string
} }
func (compo *MessageCompo) SetMessage(msg *Message) {
compo.Message = msg
}
// Return new message with the specified text. // Return new message with the specified text.
func NewMessage(text string) *MessageConfig { func NewMessage(text string) *MessageCompo {
ret := &MessageConfig{} ret := &MessageCompo{}
ret.Text = text ret.Text = text
ret.ParseMode = tgbotapi.ModeMarkdown ret.ParseMode = tgbotapi.ModeMarkdown
return ret return ret
} }
func (msg *MessageConfig) withParseMode(mode string) *MessageConfig{ func (msg *MessageCompo) withParseMode(mode string) *MessageCompo {
msg.ParseMode = mode msg.ParseMode = mode
return msg return msg
} }
// Set the default Markdown parsing mode. // Set the default Markdown parsing mode.
func (msg *MessageConfig) MD() *MessageConfig { func (msg *MessageCompo) MD() *MessageCompo {
return msg.withParseMode(tgbotapi.ModeMarkdown) return msg.withParseMode(tgbotapi.ModeMarkdown)
} }
func (msg *MessageConfig) MD2() *MessageConfig { // Set the Markdown 2 parsing mode.
func (msg *MessageCompo) MD2() *MessageCompo {
return msg.withParseMode(tgbotapi.ModeMarkdownV2) return msg.withParseMode(tgbotapi.ModeMarkdownV2)
} }
func (msg *MessageConfig) HTML() *MessageConfig { // Set the HTML parsing mode.
func (msg *MessageCompo) HTML() *MessageCompo {
return msg.withParseMode(tgbotapi.ModeHTML) return msg.withParseMode(tgbotapi.ModeHTML)
} }
func (config *MessageConfig) SendConfig( // Transform the message component into one with reply keyboard.
func (msg *MessageCompo) Inline(inline *Inline) *InlineCompo {
return &InlineCompo{
Inline: inline,
MessageCompo: msg,
}
}
// Transform the message component into one with reply keyboard.
func (msg *MessageCompo) Reply(reply *Reply) *ReplyCompo {
return &ReplyCompo{
Reply: reply,
MessageCompo: msg,
}
}
func (config *MessageCompo) SendConfig(
c *Context, c *Context,
) (*SendConfig) { ) (*SendConfig) {
var ret SendConfig var (
msg := tgbotapi.NewMessage(c.Session.Id.ToApi(), config.Text) ret SendConfig
text string
)
if config.Text == "" {
text = ">"
} else {
text = config.Text
}
msg := tgbotapi.NewMessage(c.Session.Id.ToApi(), text)
ret.Message = &msg ret.Message = &msg
ret.Message.ParseMode = config.ParseMode ret.Message.ParseMode = config.ParseMode
return &ret return &ret
} }
// Empty serving to use messages in rendering.
func (compo *MessageCompo) Serve(c *Context) {
}
func (compo *MessageCompo) Filter(_ *Update) bool {
// Skip everything
return true
}

View file

@ -50,19 +50,9 @@ func (kbd *Reply) ToApi() any {
return tgbotapi.NewReplyKeyboard(rows...) return tgbotapi.NewReplyKeyboard(rows...)
} }
// Transform the keyboard to widget with the specified text.
func (kbd *Reply) Compo(text string) *ReplyCompo {
ret := &ReplyCompo{}
ret.Reply = kbd
ret.Text = text
ret.Compo = NewCompo()
return ret
}
// The type implements reply keyboard widget. // The type implements reply keyboard widget.
type ReplyCompo struct { type ReplyCompo struct {
*Compo *MessageCompo
Text string
*Reply *Reply
} }
@ -70,27 +60,9 @@ type ReplyCompo struct {
func (compo *ReplyCompo) SendConfig( func (compo *ReplyCompo) SendConfig(
c *Context, c *Context,
) (*SendConfig) { ) (*SendConfig) {
sid := c.Session.Id.ToApi() sendConfig := compo.MessageCompo.SendConfig(c)
if compo == nil { sendConfig.Message.ReplyMarkup = compo.Reply.ToApi()
msgConfig := tgbotapi.NewMessage(sid, ">") return sendConfig
msgConfig.ReplyMarkup = tgbotapi.NewRemoveKeyboard(true)
return &SendConfig{
Message: &msgConfig,
}
}
var text string
if compo.Text != "" {
text = compo.Text
} else {
text = ">"
}
msgConfig := tgbotapi.NewMessage(sid, text)
msgConfig.ReplyMarkup = compo.ToApi()
ret := &SendConfig{}
ret.Message = &msgConfig
return ret
} }
func (compo *ReplyCompo) Filter( func (compo *ReplyCompo) Filter(
@ -134,4 +106,3 @@ func (compo *ReplyCompo) Serve(c *Context) {
} }
} }

View file

@ -12,7 +12,6 @@ type MessageId int64
type Sendable interface { type Sendable interface {
SendConfig(*Context) (*SendConfig) SendConfig(*Context) (*SendConfig)
SetMessage(*Message) SetMessage(*Message)
GetMessage() *Message
} }
type Errorer interface { type Errorer interface {