diff --git a/cmd/test/main.go b/cmd/test/main.go
index 2578832..e24f9eb 100644
--- a/cmd/test/main.go
+++ b/cmd/test/main.go
@@ -7,8 +7,8 @@ import (
"fmt"
"github.com/mojosa-software/got/tg"
- "math/rand"
- "strconv"
+ //"math/rand"
+ //"strconv"
)
type BotData struct {
@@ -20,17 +20,19 @@ type SessionData struct {
}
type MutateMessageWidget struct {
+ *tg.Compo
Mutate func(string) string
}
func NewMutateMessageWidget(fn func(string) string) *MutateMessageWidget {
ret := &MutateMessageWidget{}
+ ret.Compo = tg.NewCompo()
ret.Mutate = fn
return ret
}
func (w *MutateMessageWidget) Serve(c *tg.Context) {
- args, ok := c.Arg.(tg.ArgSlice)
+ args, ok := c.Arg.([]any)
if ok {
for _, arg := range args {
c.Sendf("%v", arg)
@@ -42,7 +44,7 @@ func (w *MutateMessageWidget) Serve(c *tg.Context) {
}
}
-func (w *MutateMessageWidget) Filter(u *tg.Update, _ tg.MessageMap) bool {
+func (w *MutateMessageWidget) Filter(u *tg.Update) bool {
if u.Message == nil {
return true
}
@@ -75,8 +77,6 @@ var (
backButton,
)
- navKeyboard =
-
sendLocationKeyboard = tg.NewKeyboard().Row(
tg.NewButton("Send location").
WithSendLocation(true).
@@ -103,17 +103,19 @@ WithInitFunc(func(c *tg.Context) {
c.Session.Data = &SessionData{}
}).WithRootNode(tg.NewRootNode(
// The "/" widget.
- tg.WidgetFunc(func(c *tg.Context) tg.UIs {return tg.UIs{
+ tg.RenderFunc(func(c *tg.Context) tg.UI {return tg.UI {
tg.NewKeyboard().Row(
tg.NewButton("GoT Github page").
WithUrl("https://github.com/mojosa-software/got"),
- ).Inline().Widget(
+ ).Inline().Compo(
fmt.Sprintf(
- "Hello, %s!\n"
- "The testing bot started!\n",
- "You can see the basics of usage in the ",
- "cmd/test/main.go file!",
+ 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,
),
),
@@ -124,72 +126,84 @@ WithInitFunc(func(c *tg.Context) {
tg.NewButton("Mutate messages").Go("/mutate-messages"),
).Row(
tg.NewButton("Send location").Go("/send-location"),
- ).Reply().Widget(
+ ).Reply().Compo(
"Choose the point of your interest",
),
}}),
tg.NewNode(
- "mutate-messages", tg.NewPage().WithReply(
- tg.NewKeyboard().Row(
- tg.NewButton("Upper case").Go("upper-case"),
- tg.NewButton("Lower case").Go("lower-case"),
- ).Row(
- backButton,
- ).Reply().Widget(
- "Choose the function to mutate string",
- ),
- ),
+ "mutate-messages", tg.RenderFunc(func(c *tg.Context) tg.UI {
+ return tg.UI{
+ tg.NewKeyboard().Row(
+ tg.NewButton("Upper case").Go("upper-case"),
+ tg.NewButton("Lower case").Go("lower-case"),
+ ).Row(
+ backButton,
+ ).Reply().Compo(
+ "Choose the function to mutate string",
+ ),
+ }
+ }),
tg.NewNode(
- "upper-case", tg.NewPage().WithReply(
- backKeyboard.Reply().
- Widget(
- "Type a string and the bot will convert it to upper case",
- ),
- ).WithSub(
- NewMutateMessageWidget(strings.ToUpper),
- ),
- ),
- tg.NewNode(
- "lower-case", tg.NewPage().WithReply(
- backKeyboard.Reply().
- Widget(
- "Type a string and the bot will convert it to lower case",
- ),
- ).WithSub(
- NewMutateMessageWidget(strings.ToLower),
- ),
- ),
- ),
-
- tg.NewNode(
- "inc-dec", tg.NewPage().WithReply(
- incDecKeyboard.Reply().Widget("Press the buttons to increment and decrement"),
- ).ActionFunc(func(c *tg.Context) {
- // The function will be calleb before serving page.
- d := ExtractSessionData(c)
- c.Sendf("Current counter value = %d", d.Counter)
+ "upper-case", tg.RenderFunc(func(c *tg.Context) tg.UI {
+ return tg.UI{
+ backKeyboard.Reply().Compo(
+ "Type a string and the bot will convert it to upper case",
+ ),
+ NewMutateMessageWidget(strings.ToUpper),
+ }
}),
+ ),
+ tg.NewNode(
+ "lower-case", tg.RenderFunc(func(c *tg.Context) tg.UI {
+ return tg.UI{
+ backKeyboard.Reply().
+ Compo(
+ "Type a string and the bot will convert it to lower case",
+ ),
+ NewMutateMessageWidget(strings.ToLower),
+ }
+ }),
+ ),
),
tg.NewNode(
- "send-location", tg.NewPage().WithReply(
- sendLocationKeyboard.Widget("Press the button to send your location!"),
- ).WithInline(
- tg.NewKeyboard().Row(
- tg.NewButton(
- "Check",
- ).WithData(
- "check",
- ).ActionFunc(func(c *tg.Context) {
- d := ExtractSessionData(c)
- c.Sendf("Counter = %d", d.Counter)
- }),
- ).Inline().Widget("Press the button to display your counter"),
- ),
+ "inc-dec", tg.RenderFunc(func(c *tg.Context) tg.UI {
+ d := ExtractSessionData(c)
+ return tg.UI{
+ incDecKeyboard.Reply().Compo(fmt.Sprintf(
+ "Press the buttons to increment and decrement.\n" +
+ "Current counter value = %d", d.Counter,
+ )),
+ }
+ }),
),
-)).WithCommands(
+
+ tg.NewNode(
+ "send-location", tg.RenderFunc(func(c *tg.Context) tg.UI {
+ return tg.UI {
+ tg.NewKeyboard().Row(
+ tg.NewButton(
+ "Check",
+ ).WithData(
+ "check",
+ ).WithAction(tg.Func(func(c *tg.Context) {
+ d := ExtractSessionData(c)
+ c.Sendf("Counter = %d", d.Counter)
+ })),
+ ).Inline().Compo("Press the button to display your counter"),
+
+ sendLocationKeyboard.Compo(
+ "Press the button to send your location!",
+ ),
+ }
+ }),
+ ),
+)).WithRoot(tg.NewCommandCompo().
+WithPreStart(tg.Func(func(c *tg.Context){
+ c.Sendf("Please, use /start ")
+})).WithCommands(
tg.NewCommand("info").
ActionFunc(func(c *tg.Context){
c.SendfHTML(`cockcock die`)
@@ -219,37 +233,15 @@ WithInitFunc(func(c *tg.Context) {
}),
tg.NewCommand("botname").
Desc("get the bot name").
- ActionFunc(func(c *tg.Context) {
+ WithAction(tg.Func(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 1 {
arg = args
}
+ return arg
+}
+
+func (c *Context) RunCompo(compo Component, args ...any) *UpdateChan {
+ s, ok := compo.(Sendable)
+ if ok {
+ msg, err := c.Send(s)
+ if err != nil {
+ panic("could not send the message")
+ }
+ s.SetMessage(msg)
+ }
+ updates := NewUpdateChan()
+ go func() {
+ compo.Serve(
+ c.WithInput(updates).
+ WithArg(c.MakeArg(args)),
+ )
+ // To let widgets finish themselves before
+ // the channel is closed and close it by themselves.
+ updates.Close()
+ }()
+ return updates
+}
+
+// Run widget in background returning the new input channel for it.
+func (c *Context) RunWidget(widget Widget, args ...any) *UpdateChan {
+ if widget == nil {
+ return nil
+ }
pth := c.Path()
- uis := widget.UI()
+ compos := widget.Render(c.WithArg(c.MakeArg(args)))
// Leave if changed path.
if pth != c.Path() {
return nil
}
- chns := make(map[UI] *UpdateChan)
- for _, ui := range uis {
- s, ok := ui.(Sendable)
- if ok {
- msg := c.Send(s.SendConfig(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 and close it by themselves.
- updates.Close()
- }()
- chns[ui] = updates
+ chns := make([]*UpdateChan, len(compos))
+ for i, compo := range compos {
+ chns[i] = c.RunCompo(compo)
}
ret := NewUpdateChan()
go func() {
- for u := range ret {
- for ui := range uis {
- if !ui.Filter() {
- chns[ui] <- u
+ for u := range ret.Chan() {
+ for i, compo := range compos {
+ chn := chns[i]
+ if !compo.Filter(u) {
+ chn.Send(u)
}
}
}
@@ -239,7 +249,7 @@ func (c *Context) runWidget(widget Widget, args ...any) *UpdateChan {
for _, chn := range chns {
chn.Close()
}
- }
+ }()
return ret
}
diff --git a/tg/file.go b/tg/file.go
index 9ea91ef..e0d8e1d 100644
--- a/tg/file.go
+++ b/tg/file.go
@@ -23,15 +23,18 @@ var (
)
type File struct {
+ *Compo
path string
typ FileType
caption string
}
func NewFile(path string) *File {
- return &File{
+ ret := &File{
path: path,
}
+ ret.Compo = NewCompo()
+ return ret
}
func (f *File) withType(typ FileType) *File {
@@ -73,14 +76,14 @@ func (f *File) SendData() string {
return ""
}
func (f *File) SendConfig(
- sid SessionId, bot *Bot,
+ c *Context,
) (*SendConfig) {
var config SendConfig
- cid := sid.ToApi()
+ sid := c.Session.Id.ToApi()
switch f.Type() {
case ImageFileType:
- photo := tgbotapi.NewPhoto(cid, f)
+ photo := tgbotapi.NewPhoto(sid, f)
photo.Caption = f.caption
config.Image = &photo
diff --git a/tg/filter.go b/tg/filter.go
index e0b51aa..cafe364 100644
--- a/tg/filter.go
+++ b/tg/filter.go
@@ -10,9 +10,9 @@ type Filterer interface {
Filter(*Update) bool
}
-type FilterFunc func(*Update, MessageMap) bool
+type FilterFunc func(*Update) bool
func (f FilterFunc) Filter(
- u *Update, msgs MessageMap,
+ u *Update,
) bool {
- return f(u, msgs)
+ return f(u)
}
diff --git a/tg/inline.go b/tg/inline.go
index 3759a2f..1cbfb9a 100644
--- a/tg/inline.go
+++ b/tg/inline.go
@@ -25,15 +25,16 @@ func (kbd *Inline) ToApi() tgbotapi.InlineKeyboardMarkup {
// Transform the keyboard to widget with the specified text.
func (kbd *Inline) Compo(text string) *InlineCompo {
- ret := &InlinCompo{}
+ ret := &InlineCompo{}
ret.Inline = kbd
ret.Text = text
+ ret.Compo = NewCompo()
return ret
}
// The type implements message with an inline keyboard.
type InlineCompo struct {
- Compo
+ *Compo
Text string
*Inline
}
@@ -87,13 +88,13 @@ func (widget *InlineCompo) Serve(c *Context) {
} else if widget.Action != nil {
act = widget.Action
}
- c.Run(act, u)
+ c.WithUpdate(u).Run(act)
}
}
// Implementing the Filterer interface.
func (compo *InlineCompo) Filter(u *Update) bool {
- if widget == nil || u.CallbackQuery == nil {
+ if compo == nil || u.CallbackQuery == nil {
return true
}
diff --git a/tg/message.go b/tg/message.go
index 206fa3c..10dcbb8 100644
--- a/tg/message.go
+++ b/tg/message.go
@@ -6,6 +6,7 @@ import (
// Simple text message type.
type MessageConfig struct {
+ Compo
ParseMode string
Text string
}
@@ -38,10 +39,10 @@ func (msg *MessageConfig) HTML() *MessageConfig {
}
func (config *MessageConfig) SendConfig(
- sid SessionId, bot *Bot,
+ c *Context,
) (*SendConfig) {
var ret SendConfig
- msg := tgbotapi.NewMessage(sid.ToApi(), config.Text)
+ msg := tgbotapi.NewMessage(c.Session.Id.ToApi(), config.Text)
ret.Message = &msg
ret.Message.ParseMode = config.ParseMode
return &ret
diff --git a/tg/read.go b/tg/read.go
index e221646..77cfbc1 100644
--- a/tg/read.go
+++ b/tg/read.go
@@ -1,52 +1,3 @@
package tg
-// The type to descsribe one line reading widget.
-type UpdateRead struct {
- Pre Action
- Filterer Filterer
- Post Widget
-}
-
-func (rd *UpdateRead) Filter(u *Update, msgs MessageMap) bool {
- if rd.Filterer != nil {
- return rd.Filterer.Filter(u, msgs)
- }
-
- return false
-}
-
-// Returns new empty update reader.
-func NewUpdateRead(filter Filterer, post Widget) *UpdateRead {
- ret := &UpdateRead{}
- ret.Filterer = filter
- ret.Post = post
- return ret
-}
-
-func (rd *UpdateRead) WithPre(a Action) *UpdateRead {
- rd.Pre = a
- return rd
-}
-
-func NewTextMessageRead(pre Action, post Widget) *UpdateRead {
- ret := NewUpdateRead(
- FilterFunc(func(u *Update, _ MessageMap) bool {
- return u.Message == nil
- }),
- post,
- ).WithPre(pre)
- return ret
-}
-
-func (rd *UpdateRead) Serve(c *Context) {
- c.Run(rd.Pre, c.Update)
- for u := range c.Input() {
- if rd.Filter(u, nil) {
- continue
- }
- c.RunWidget(rd.Post, u)
- break
- }
-}
-
diff --git a/tg/reply.go b/tg/reply.go
index 3dc7467..7727dde 100644
--- a/tg/reply.go
+++ b/tg/reply.go
@@ -55,17 +55,19 @@ 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.
type ReplyCompo struct {
+ *Compo
Text string
*Reply
}
// Implementing the sendable interface.
-func (compo *ReplyCompo) Render(
+func (compo *ReplyCompo) SendConfig(
c *Context,
) (*SendConfig) {
sid := c.Session.Id.ToApi()
@@ -98,10 +100,10 @@ func (compo *ReplyCompo) Filter(
return true
}
- _, ok := widget.ButtonMap()[u.Message.Text]
+ _, ok := compo.ButtonMap()[u.Message.Text]
if !ok {
if u.Message.Location != nil {
- locBtn := widget.ButtonMap().LocationButton()
+ locBtn := compo.ButtonMap().LocationButton()
if locBtn == nil {
return true
}
@@ -117,7 +119,7 @@ func (compo *ReplyCompo) Serve(c *Context) {
for u := range c.Input() {
var btn *Button
text := u.Message.Text
- btns := widget.ButtonMap()
+ btns := compo.ButtonMap()
btn, ok := btns[text]
if !ok {
@@ -127,7 +129,7 @@ func (compo *ReplyCompo) Serve(c *Context) {
}
if btn != nil {
- c.Run(btn.Action, u)
+ c.WithUpdate(u).Run(btn.Action)
}
}
}
diff --git a/tg/session.go b/tg/session.go
index bfdb8f4..2ffa67d 100644
--- a/tg/session.go
+++ b/tg/session.go
@@ -4,7 +4,7 @@ package tg
// related to.
type SessionScope uint8
const (
- NoSessionScope ContextScope = iota
+ NoSessionScope SessionScope = iota
PrivateSessionScope
GroupSessionScope
ChannelSessionScope
@@ -30,9 +30,10 @@ type Session struct {
}
// Return new empty session with specified user ID.
-func NewSession(id SessionId) *Session {
+func NewSession(id SessionId, scope SessionScope) *Session {
return &Session{
Id: id,
+ Scope: scope,
}
}
@@ -41,9 +42,9 @@ func NewSession(id SessionId) *Session {
type SessionMap map[SessionId]*Session
// Add new empty session by it's ID.
-func (sm SessionMap) Add(sid SessionId) *Session {
- ret := NewSession(sid)
- sm[sid] = NewSession(sid)
+func (sm SessionMap) Add(sid SessionId, scope SessionScope) *Session {
+ ret := NewSession(sid, scope)
+ sm[sid] = ret
return ret
}