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 }