KEEP IMPLEMENTING THE NEW COMPONENT SYSTEM INSTEAD OF OLD WIDGETS.

This commit is contained in:
Andrey Parhomenko 2023-09-25 23:43:22 +03:00
parent 185c8fc1f8
commit 1f52474082
12 changed files with 192 additions and 151 deletions

View file

@ -7,6 +7,8 @@ import (
"fmt" "fmt"
"github.com/mojosa-software/got/tg" "github.com/mojosa-software/got/tg"
"math/rand"
"strconv"
) )
type BotData struct { type BotData struct {
@ -73,13 +75,7 @@ var (
backButton, backButton,
) )
navKeyboard = tg.NewKeyboard().Row( navKeyboard =
tg.NewButton("Inc/Dec").Go("/inc-dec"),
).Row(
tg.NewButton("Mutate messages").Go("/mutate-messages"),
).Row(
tg.NewButton("Send location").Go("/send-location"),
).Reply()
sendLocationKeyboard = tg.NewKeyboard().Row( sendLocationKeyboard = tg.NewKeyboard().Row(
tg.NewButton("Send location"). tg.NewButton("Send location").
@ -107,22 +103,35 @@ WithInitFunc(func(c *tg.Context) {
c.Session.Data = &SessionData{} c.Session.Data = &SessionData{}
}).WithRootNode(tg.NewRootNode( }).WithRootNode(tg.NewRootNode(
// The "/" widget. // The "/" widget.
tg.NewPage(). tg.WidgetFunc(func(c *tg.Context) tg.UIs {
WithInline( return tg.UIs{
tg.NewKeyboard().Row( 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().Widget( ).Inline().Widget(
fmt.Sprint( fmt.Sprintf(
"Hello, %s"
"The testing bot started!\n", "The testing bot started!\n",
"You can see the basics of usage in the ", "You can see the basics of usage in the ",
"cmd/test/main.go file!", "cmd/test/main.go file!",
c.SentFrom().UserName,
), ),
), ),
).WithReply(
navKeyboard.Widget("Choose what you are interested in"), 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().Widget(
"Choose the point of your interest",
), ),
}
)
tg.NewNode( tg.NewNode(
"mutate-messages", tg.NewPage().WithReply( "mutate-messages", tg.NewPage().WithReply(
tg.NewKeyboard().Row( tg.NewKeyboard().Row(
@ -216,6 +225,32 @@ WithInitFunc(func(c *tg.Context) {
bd := c.Bot.Data.(*BotData) bd := c.Bot.Data.(*BotData)
c.Sendf("My name is %q", bd.Name) c.Sendf("My name is %q", bd.Name)
}), }),
tg.NewCommand("dynamic").
Desc("check of the dynamic work").
WidgetFunc(func(c *tg.Context){
nRow, nBtn := rand.Int()%10, rand.Int()%5
rows := []tg.ButtonRow{}
for i:=0 ; i<nRow ; i++ {
row := []*tg.Button{}
for j:=0 ; j<nBtn ; j++ {
row = append(row, tg.NewButton(
strconv.Itoa(i) + " " + strconv.Itoa(j),
))
}
rows = append(rows, row)
}
kbd := tg.NewKeyboard(rows...).ActionFunc(func(c *tg.Context){
c.Sendf(u.)
})Inline().Widget("sample text")
c.Send(kbd)
kbdChn := c.RunWidget(kbd)
for u := range c.Input() {
if kbd.Filter(u, nil) {
continue
}
kbdChn.Send(u)
}
}),
) )
var gBeh = tg.NewGroupBehaviour(). var gBeh = tg.NewGroupBehaviour().

View file

@ -47,10 +47,12 @@ func (bot *Bot) Debug(debug bool) *Bot {
return bot return bot
} }
// Send the Renderable to the specified session client side.
// Can be used for both group and private sessions.
func (bot *Bot) Send( func (bot *Bot) Send(
sid SessionId, v Sendable, sid SessionId, v Renderable,
) (*Message, error) { ) (*Message, error) {
config := v.SendConfig(sid, bot) config := v.Render(sid, bot)
if config.Error != nil { if config.Error != nil {
return nil, config.Error return nil, config.Error
} }

42
tg/compo.go Normal file
View file

@ -0,0 +1,42 @@
package tg
type UIs []UI
// The type describes dynamic screen widget.
type Widget interface {
UIs(*Context) UIs
}
// The way to describe custom function based Widgets.
type WidgetFunc func(c *Context) UIs
func (fn WidgetFunc) UIs(c *Context) UIs {
return fn(c)
}
// The type describes interfaces
// needed to be implemented to be endpoint handlers.
type UI interface {
Renderable
SetMessage(*Message)
GetMessage() *Message
Filterer
Server
}
type UiFunc func()
// The type to embed into potential components.
// Implements empty versions of interfaces
// and contains
type Compo struct{
*Message
}
// 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, _ *Message) bool {return false}

View file

@ -7,6 +7,15 @@ import (
//"path" //"path"
) )
// General type function for faster typing.
type Func func(*Context)
func (f Func) Act(c *Context) {
f(c)
}
func (f Func) Serve(c *Context) {
f(c)
}
// The way to determine where the context is // The way to determine where the context is
// related to. // related to.
type ContextScope uint8 type ContextScope uint8
@ -185,7 +194,7 @@ func (c *Context) PathExist(pth Path) bool {
} }
// Run widget in background returning the new input channel for it. // Run widget in background returning the new input channel for it.
func (c *Context) RunWidget(widget Widget, args ...any) *UpdateChan { func (c *Context) runWidget(widget Widget, args ...any) {
if widget == nil { if widget == nil {
return nil return nil
} }
@ -197,9 +206,14 @@ func (c *Context) RunWidget(widget Widget, args ...any) *UpdateChan {
arg = args arg = args
} }
uis := widget.UI()
chns := make(map[UI] *UpdateChan)
for _, ui := range uis {
msg := c.Send(ui.Render(c))
ui.SetMessage(msg)
updates := NewUpdateChan() updates := NewUpdateChan()
go func() { go func() {
widget.Serve( ui.Serve(
c.Copy(). c.Copy().
WithInput(updates). WithInput(updates).
WithArg(arg), WithArg(arg),
@ -208,8 +222,16 @@ func (c *Context) RunWidget(widget Widget, args ...any) *UpdateChan {
// the channel is closed. // the channel is closed.
updates.Close() updates.Close()
}() }()
chns[ui] = updates
}
return updates for u := range c.skippedUpdates.Chan() {
for ui := range uis {
if !ui.Filter() {
chns[ui] <- u
}
}
}
} }
// Simple way to read strings for widgets. // Simple way to read strings for widgets.

18
tg/filter.go Normal file
View file

@ -0,0 +1,18 @@
package tg
// Implementing the interface provides way
// to know exactly what kind of updates
// the widget needs.
type Filterer interface {
// Return true if should filter the update
// and not send it inside the widget.
Filter(*Update) bool
}
type FilterFunc func(*Update, MessageMap) bool
func (f FilterFunc) Filter(
u *Update, msgs MessageMap,
) bool {
return f(u, msgs)
}

View file

@ -32,15 +32,15 @@ func (kbd *Inline) ToApi() tgbotapi.InlineKeyboardMarkup {
} }
// The type implements message with an inline keyboard. // The type implements message with an inline keyboard.
type InlineWidget struct { type InlineCompo struct {
Compo
Text string Text string
*Inline *Inline
} }
// Implementing the Sendable interface. // Implementing the Sendable interface.
func (widget *InlineWidget) SendConfig( func (widget *InlineWidget) SendConfig(
sid SessionId, c *Context,
bot *Bot,
) (*SendConfig) { ) (*SendConfig) {
var text string var text string
if widget.Text != "" { if widget.Text != "" {
@ -49,7 +49,8 @@ func (widget *InlineWidget) SendConfig(
text = ">" text = ">"
} }
msgConfig := tgbotapi.NewMessage(sid.ToApi(), text) sid := c.Session.Id.ToApi()
msgConfig := tgbotapi.NewMessage(sid, text)
msgConfig.ReplyMarkup = widget.ToApi() msgConfig.ReplyMarkup = widget.ToApi()
ret := &SendConfig{} ret := &SendConfig{}
@ -58,7 +59,7 @@ func (widget *InlineWidget) SendConfig(
} }
// Implementing the Widget interface. // Implementing the Widget interface.
func (widget *InlineWidget) 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 { if u.CallbackQuery == nil {
@ -90,20 +91,13 @@ func (widget *InlineWidget) Serve(c *Context) {
} }
} }
func (widget *InlineWidget) Filter( func (compo *InlineCompo) Filter(u *Update) bool {
u *Update,
msgs MessageMap,
) bool {
if widget == nil || u.CallbackQuery == nil { if widget == nil || u.CallbackQuery == nil {
return true return true
} }
inlineMsg, inlineOk := msgs[""]
if !inlineOk {
return true
}
if u.CallbackQuery.Message.MessageID != if u.CallbackQuery.Message.MessageID !=
inlineMsg.MessageID { compo.Message.MessageID {
return true return true
} }

2
tg/make.go Normal file
View file

@ -0,0 +1,2 @@
package tg

View file

@ -3,9 +3,7 @@ package tg
import ( import (
//tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" //tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
) )
/*
// The basic widget to provide keyboard functionality
// without implementing much.
type Page struct { type Page struct {
Action Action Action Action
Text string Text string
@ -129,5 +127,5 @@ func (p *Page) Serve(c *Context) {
} }
} }
} }
*/

View file

@ -59,51 +59,45 @@ func (kbd *Reply) Widget(text string) *ReplyWidget {
} }
// The type implements reply keyboard widget. // The type implements reply keyboard widget.
type ReplyWidget struct { type ReplyCompo struct {
Text string Text string
*Reply *Reply
} }
// Implementing the sendable interface. // Implementing the sendable interface.
func (widget *ReplyWidget) SendConfig( func (compo *ReplyCompo) Render(
sid SessionId, c *Context,
bot *Bot,
) (*SendConfig) { ) (*SendConfig) {
if widget == nil { sid := c.Session.Id.ToApi()
msgConfig := tgbotapi.NewMessage(sid.ToApi(), ">") if compo == nil {
msgConfig := tgbotapi.NewMessage(sid, ">")
msgConfig.ReplyMarkup = tgbotapi.NewRemoveKeyboard(true) msgConfig.ReplyMarkup = tgbotapi.NewRemoveKeyboard(true)
return &SendConfig{ return &SendConfig{
Message: &msgConfig, Message: &msgConfig,
} }
} }
var text string var text string
if widget.Text != "" { if compo.Text != "" {
text = widget.Text text = compo.Text
} else { } else {
text = ">" text = ">"
} }
msgConfig := tgbotapi.NewMessage(sid.ToApi(), text) msgConfig := tgbotapi.NewMessage(sid, text)
msgConfig.ReplyMarkup = widget.ToApi() msgConfig.ReplyMarkup = compo.ToApi()
ret := &SendConfig{} ret := &SendConfig{}
ret.Message = &msgConfig ret.Message = &msgConfig
return ret return ret
} }
func (widget *ReplyWidget) Filter( func (compo *ReplyCompo) Filter(
u *Update, u *Update,
msgs MessageMap,
) bool { ) bool {
if widget == nil { if compo == nil || u.Message == nil {
return true return true
} }
if u.Message == nil {
return true
}
_, ok := widget.ButtonMap()[u.Message.Text] _, ok := widget.ButtonMap()[u.Message.Text]
if !ok { if !ok {
if u.Message.Location != nil { if u.Message.Location != nil {
@ -119,11 +113,8 @@ func (widget *ReplyWidget) Filter(
} }
// Implementing the Widget interface. // Implementing the Widget interface.
func (widget *ReplyWidget) Serve(c *Context) { func (compo *ReplyCompo) Serve(c *Context) {
for u := range c.Input() { for u := range c.Input() {
if u.Message == nil || u.Message.Text == "" {
continue
}
var btn *Button var btn *Button
text := u.Message.Text text := u.Message.Text
btns := widget.ButtonMap() btns := widget.ButtonMap()

View file

@ -5,16 +5,12 @@ import (
) )
type MessageId int64 type MessageId int64
type Image any
// Implementing the interface lets the
// value to be sent.
type Sendable interface {
SendConfig(SessionId, *Bot) *SendConfig
}
// Implementing the interface provides
// way to define what message will be
// sent to the side of a user.
type Renderable interface { type Renderable interface {
Render(SessionId, *Bot) ([]*SendConfig) Render(*Context) (*SendConfig)
} }
type Errorer interface { type Errorer interface {

8
tg/server.go Normal file
View file

@ -0,0 +1,8 @@
package tg
// Implementing the interface provides
// the way to define how to handle updates.
type Server interface {
Serve(*Context)
}

View file

@ -1,77 +1,10 @@
package tg package tg
import (
//tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
//"fmt"
)
type Maker[V any] interface { type Maker[V any] interface {
Make(*Context) V Make(*Context) V
} }
type MakeFunc[V any] func(*Context) V type RootHandler interface {
func (fn MakeFunc[V]) Make(c *Context) V {
return fn(c)
}
type ArgMap = map[string] any
type ArgSlice = []any
type ArgList[V any] []V
// 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)
}
type DynamicWidget[W Widget] interface {
Maker[W]
}
// Implementing the interface provides ability to
// be used as the root widget for contexts.
type RootWidget interface {
Widget
SetSub(Widget)
}
// Implementing the interface provides way
// to know exactly what kind of updates
// the widget needs.
type Filterer interface {
// Return true if should filter the update
// and not send it inside the widget.
Filter(*Update, MessageMap) bool
}
type FilterFunc func(*Update, MessageMap) bool
func (f FilterFunc) Filter(
u *Update, msgs MessageMap,
) bool {
return f(u, msgs)
}
// General type function for faster typing.
type Func func(*Context)
func (f Func) Act(c *Context) {
f(c)
}
func (f Func) Serve(c *Context) {
f(c)
}
// The function that implements the Widget
// interface.
type WidgetFunc func(*Context)
func (wf WidgetFunc) Serve(c *Context) {
wf(c)
} }