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"
"github.com/mojosa-software/got/tg"
"math/rand"
"strconv"
)
type BotData struct {
@ -73,13 +75,7 @@ var (
backButton,
)
navKeyboard = 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()
navKeyboard =
sendLocationKeyboard = tg.NewKeyboard().Row(
tg.NewButton("Send location").
@ -107,21 +103,34 @@ WithInitFunc(func(c *tg.Context) {
c.Session.Data = &SessionData{}
}).WithRootNode(tg.NewRootNode(
// The "/" widget.
tg.NewPage().
WithInline(
tg.WidgetFunc(func(c *tg.Context) tg.UIs {
return tg.UIs{
tg.NewKeyboard().Row(
tg.NewButton("GoT Github page").
WithUrl("https://github.com/mojosa-software/got"),
).Inline().Widget(
fmt.Sprint(
"The testing bot started!\n",
"You can see the basics of usage in the ",
"cmd/test/main.go file!",
tg.NewButton("GoT Github page").
WithUrl("https://github.com/mojosa-software/got"),
).Inline().Widget(
fmt.Sprintf(
"Hello, %s"
"The testing bot started!\n",
"You can see the basics of usage in the ",
"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(
"mutate-messages", tg.NewPage().WithReply(
@ -216,6 +225,32 @@ WithInitFunc(func(c *tg.Context) {
bd := c.Bot.Data.(*BotData)
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().

View file

@ -47,10 +47,12 @@ func (bot *Bot) Debug(debug bool) *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(
sid SessionId, v Sendable,
sid SessionId, v Renderable,
) (*Message, error) {
config := v.SendConfig(sid, bot)
config := v.Render(sid, bot)
if config.Error != nil {
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"
)
// 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
// related to.
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.
func (c *Context) RunWidget(widget Widget, args ...any) *UpdateChan {
func (c *Context) runWidget(widget Widget, args ...any) {
if widget == nil {
return nil
}
@ -197,19 +206,32 @@ func (c *Context) RunWidget(widget Widget, args ...any) *UpdateChan {
arg = args
}
updates := NewUpdateChan()
go func() {
widget.Serve(
c.Copy().
WithInput(updates).
WithArg(arg),
)
// To let widgets finish themselves before
// the channel is closed.
updates.Close()
}()
uis := widget.UI()
chns := make(map[UI] *UpdateChan)
for _, ui := range uis {
msg := c.Send(ui.Render(c))
ui.SetMessage(msg)
updates := NewUpdateChan()
go func() {
ui.Serve(
c.Copy().
WithInput(updates).
WithArg(arg),
)
// To let widgets finish themselves before
// the channel is closed.
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.

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.
type InlineWidget struct {
type InlineCompo struct {
Compo
Text string
*Inline
}
// Implementing the Sendable interface.
func (widget *InlineWidget) SendConfig(
sid SessionId,
bot *Bot,
c *Context,
) (*SendConfig) {
var text string
if widget.Text != "" {
@ -49,7 +49,8 @@ func (widget *InlineWidget) SendConfig(
text = ">"
}
msgConfig := tgbotapi.NewMessage(sid.ToApi(), text)
sid := c.Session.Id.ToApi()
msgConfig := tgbotapi.NewMessage(sid, text)
msgConfig.ReplyMarkup = widget.ToApi()
ret := &SendConfig{}
@ -58,7 +59,7 @@ func (widget *InlineWidget) SendConfig(
}
// Implementing the Widget interface.
func (widget *InlineWidget) Serve(c *Context) {
func (widget *InlineCompo) Serve(c *Context) {
for u := range c.Input() {
var act Action
if u.CallbackQuery == nil {
@ -90,20 +91,13 @@ func (widget *InlineWidget) Serve(c *Context) {
}
}
func (widget *InlineWidget) Filter(
u *Update,
msgs MessageMap,
) bool {
func (compo *InlineCompo) Filter(u *Update) bool {
if widget == nil || u.CallbackQuery == nil {
return true
}
inlineMsg, inlineOk := msgs[""]
if !inlineOk {
return true
}
if u.CallbackQuery.Message.MessageID !=
inlineMsg.MessageID {
compo.Message.MessageID {
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 (
//tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
// The basic widget to provide keyboard functionality
// without implementing much.
/*
type Page struct {
Action Action
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.
type ReplyWidget struct {
type ReplyCompo struct {
Text string
*Reply
}
// Implementing the sendable interface.
func (widget *ReplyWidget) SendConfig(
sid SessionId,
bot *Bot,
func (compo *ReplyCompo) Render(
c *Context,
) (*SendConfig) {
if widget == nil {
msgConfig := tgbotapi.NewMessage(sid.ToApi(), ">")
sid := c.Session.Id.ToApi()
if compo == nil {
msgConfig := tgbotapi.NewMessage(sid, ">")
msgConfig.ReplyMarkup = tgbotapi.NewRemoveKeyboard(true)
return &SendConfig{
Message: &msgConfig,
}
}
var text string
if widget.Text != "" {
text = widget.Text
if compo.Text != "" {
text = compo.Text
} else {
text = ">"
}
msgConfig := tgbotapi.NewMessage(sid.ToApi(), text)
msgConfig.ReplyMarkup = widget.ToApi()
msgConfig := tgbotapi.NewMessage(sid, text)
msgConfig.ReplyMarkup = compo.ToApi()
ret := &SendConfig{}
ret.Message = &msgConfig
return ret
}
func (widget *ReplyWidget) Filter(
func (compo *ReplyCompo) Filter(
u *Update,
msgs MessageMap,
) bool {
if widget == nil {
if compo == nil || u.Message == nil {
return true
}
if u.Message == nil {
return true
}
_, ok := widget.ButtonMap()[u.Message.Text]
if !ok {
if u.Message.Location != nil {
@ -119,11 +113,8 @@ func (widget *ReplyWidget) Filter(
}
// Implementing the Widget interface.
func (widget *ReplyWidget) Serve(c *Context) {
func (compo *ReplyCompo) Serve(c *Context) {
for u := range c.Input() {
if u.Message == nil || u.Message.Text == "" {
continue
}
var btn *Button
text := u.Message.Text
btns := widget.ButtonMap()

View file

@ -5,16 +5,12 @@ import (
)
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 {
Render(SessionId, *Bot) ([]*SendConfig)
Render(*Context) (*SendConfig)
}
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
import (
//tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
//"fmt"
)
type Maker[V any] interface {
Make(*Context) V
}
type MakeFunc[V any] func(*Context) V
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)
type RootHandler interface {
}