Finally implemented at least not stable but working version.

This commit is contained in:
Andrey Parhomenko 2023-09-27 14:09:49 +03:00
parent abf080164a
commit 0aaabff503
13 changed files with 206 additions and 249 deletions

View file

@ -7,8 +7,8 @@ import (
"fmt" "fmt"
"github.com/mojosa-software/got/tg" "github.com/mojosa-software/got/tg"
"math/rand" //"math/rand"
"strconv" //"strconv"
) )
type BotData struct { type BotData struct {
@ -20,17 +20,19 @@ 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
} }
func (w *MutateMessageWidget) Serve(c *tg.Context) { func (w *MutateMessageWidget) Serve(c *tg.Context) {
args, ok := c.Arg.(tg.ArgSlice) args, ok := c.Arg.([]any)
if ok { if ok {
for _, arg := range args { for _, arg := range args {
c.Sendf("%v", arg) 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 { if u.Message == nil {
return true return true
} }
@ -75,8 +77,6 @@ var (
backButton, backButton,
) )
navKeyboard =
sendLocationKeyboard = tg.NewKeyboard().Row( sendLocationKeyboard = tg.NewKeyboard().Row(
tg.NewButton("Send location"). tg.NewButton("Send location").
WithSendLocation(true). WithSendLocation(true).
@ -103,17 +103,19 @@ WithInitFunc(func(c *tg.Context) {
c.Session.Data = &SessionData{} c.Session.Data = &SessionData{}
}).WithRootNode(tg.NewRootNode( }).WithRootNode(tg.NewRootNode(
// The "/" widget. // 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.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().Compo(
fmt.Sprintf( fmt.Sprintf(
"Hello, %s!\n" fmt.Sprint(
"Hello, %s!\n",
"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, c.SentFrom().UserName,
), ),
), ),
@ -124,72 +126,84 @@ WithInitFunc(func(c *tg.Context) {
tg.NewButton("Mutate messages").Go("/mutate-messages"), tg.NewButton("Mutate messages").Go("/mutate-messages"),
).Row( ).Row(
tg.NewButton("Send location").Go("/send-location"), tg.NewButton("Send location").Go("/send-location"),
).Reply().Widget( ).Reply().Compo(
"Choose the point of your interest", "Choose the point of your interest",
), ),
}}), }}),
tg.NewNode( tg.NewNode(
"mutate-messages", tg.NewPage().WithReply( "mutate-messages", tg.RenderFunc(func(c *tg.Context) tg.UI {
return tg.UI{
tg.NewKeyboard().Row( tg.NewKeyboard().Row(
tg.NewButton("Upper case").Go("upper-case"), tg.NewButton("Upper case").Go("upper-case"),
tg.NewButton("Lower case").Go("lower-case"), tg.NewButton("Lower case").Go("lower-case"),
).Row( ).Row(
backButton, backButton,
).Reply().Widget( ).Reply().Compo(
"Choose the function to mutate string", "Choose the function to mutate string",
), ),
), }
}),
tg.NewNode( tg.NewNode(
"upper-case", tg.NewPage().WithReply( "upper-case", tg.RenderFunc(func(c *tg.Context) tg.UI {
backKeyboard.Reply(). return tg.UI{
Widget( backKeyboard.Reply().Compo(
"Type a string and the bot will convert it to upper case", "Type a string and the bot will convert it to upper case",
), ),
).WithSub(
NewMutateMessageWidget(strings.ToUpper), NewMutateMessageWidget(strings.ToUpper),
), }
}),
), ),
tg.NewNode( tg.NewNode(
"lower-case", tg.NewPage().WithReply( "lower-case", tg.RenderFunc(func(c *tg.Context) tg.UI {
return tg.UI{
backKeyboard.Reply(). backKeyboard.Reply().
Widget( 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",
), ),
).WithSub(
NewMutateMessageWidget(strings.ToLower), NewMutateMessageWidget(strings.ToLower),
), }
}),
), ),
), ),
tg.NewNode( tg.NewNode(
"inc-dec", tg.NewPage().WithReply( "inc-dec", tg.RenderFunc(func(c *tg.Context) tg.UI {
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) d := ExtractSessionData(c)
c.Sendf("Current counter value = %d", d.Counter) return tg.UI{
incDecKeyboard.Reply().Compo(fmt.Sprintf(
"Press the buttons to increment and decrement.\n" +
"Current counter value = %d", d.Counter,
)),
}
}), }),
), ),
tg.NewNode( tg.NewNode(
"send-location", tg.NewPage().WithReply( "send-location", tg.RenderFunc(func(c *tg.Context) tg.UI {
sendLocationKeyboard.Widget("Press the button to send your location!"), return tg.UI {
).WithInline(
tg.NewKeyboard().Row( tg.NewKeyboard().Row(
tg.NewButton( tg.NewButton(
"Check", "Check",
).WithData( ).WithData(
"check", "check",
).ActionFunc(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"),
sendLocationKeyboard.Compo(
"Press the button to send your location!",
),
}
}), }),
).Inline().Widget("Press the button to display your counter"),
), ),
), )).WithRoot(tg.NewCommandCompo().
)).WithCommands( WithPreStart(tg.Func(func(c *tg.Context){
c.Sendf("Please, use /start ")
})).WithCommands(
tg.NewCommand("info"). tg.NewCommand("info").
ActionFunc(func(c *tg.Context){ ActionFunc(func(c *tg.Context){
c.SendfHTML(`<a href="https://res.cloudinary.com/demo/image/upload/v1312461204/sample.jpg">cock</a><strong>cock</strong> die`) c.SendfHTML(`<a href="https://res.cloudinary.com/demo/image/upload/v1312461204/sample.jpg">cock</a><strong>cock</strong> die`)
@ -219,37 +233,15 @@ WithInitFunc(func(c *tg.Context) {
}), }),
tg.NewCommand("botname"). tg.NewCommand("botname").
Desc("get the bot name"). Desc("get the bot name").
ActionFunc(func(c *tg.Context) { WithAction(tg.Func(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"). tg.NewCommand("dynamic").
Desc("check of the dynamic work"). Desc("check of the dynamic work").
WidgetFunc(func(c *tg.Context){ WithWidget(tg.Func(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().
InitFunc(func(c *tg.GC) { InitFunc(func(c *tg.GC) {

View file

@ -5,7 +5,7 @@ package tg
// The type describes behaviour for the bot in personal chats. // The type describes behaviour for the bot in personal chats.
type Behaviour struct { type Behaviour struct {
Root Widget Root Component
Init Action Init Action
Screens ScreenMap Screens ScreenMap
} }
@ -17,12 +17,6 @@ func NewBehaviour() *Behaviour {
} }
} }
// Set the root widget. Mostly the CommandWidget is used.
func (b *Behaviour) WithRoot(root Widget) *Behaviour {
b.Root = root
return b
}
// The Action will be called on session creation, // The Action will be called on session creation,
// not when starting or restarting the bot with the Start Action. // not when starting or restarting the bot with the Start Action.
func (b *Behaviour) WithInit(a Action) *Behaviour { func (b *Behaviour) WithInit(a Action) *Behaviour {
@ -61,15 +55,8 @@ func (b *Behaviour) WithRootNode(node *RootNode) *Behaviour {
// The function sets as the standard root widget CommandWidget // The function sets as the standard root widget CommandWidget
// and its commands.. // and its commands..
func (b *Behaviour) WithCommands(cmds ...*Command) *Behaviour { func (b *Behaviour) WithRoot(root Component) *Behaviour {
b.Root = NewCommandWidget(). b.Root = root
WithCommands(cmds...).
WithPreStartFunc(func(c *Context){
c.Sendf("Please, use the /start command to start")
}).WithUsageFunc(func(c *Context){
c.Sendf("No such command")
})
return b return b
} }

View file

@ -39,6 +39,7 @@ func NewBot(token string) (*Bot, error) {
return &Bot{ return &Bot{
Api: bot, Api: bot,
contexts: make(map[SessionId] *context),
}, nil }, nil
} }
@ -50,14 +51,18 @@ 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.
func (bot *Bot) Send( func (bot *Bot) Send(
sid SessionId, v Sendable, sid SessionId, v Sendable, args ...any,
) (*Message, error) { ) (*Message, error) {
c, ok := bot.contexts[sid] ctx, ok := bot.contexts[sid]
if !ok { if !ok {
return nil, ContextNotExistErr return nil, ContextNotExistErr
} }
config := v.Render(sid, bot) c := &Context{
context: ctx,
}
config := v.SendConfig(c.WithArg(c.MakeArg(args)))
if config.Error != nil { if config.Error != nil {
return nil, config.Error return nil, config.Error
} }
@ -220,18 +225,19 @@ func (bot *Bot) handlePrivate(updates chan *Update) {
sid = SessionId(u.FromChat().ID) sid = SessionId(u.FromChat().ID)
ctx, ctxOk := bot.contexts[sid] ctx, ctxOk := bot.contexts[sid]
if u.Message != nil && !ctxOk { if u.Message != nil && !ctxOk {
// Create context on any message
// if we have no one.
session, sessionOk := bot.sessions[sid] session, sessionOk := bot.sessions[sid]
if !sessionOk { if !sessionOk {
// Creating session if we have none. // Creating session if we have none.
session = bot.sessions.Add(sid) session = bot.sessions.Add(sid, PrivateSessionScope)
} }
session = bot.sessions[sid] session = bot.sessions[sid]
// Create context on any message
// if we have no one.
ctx = &context{ ctx = &context{
Bot: bot, Bot: bot,
Session: session, Session: session,
scope: PrivateContextScope,
updates: NewUpdateChan(), updates: NewUpdateChan(),
} }
if !ctxOk { if !ctxOk {

View file

@ -100,21 +100,21 @@ func (c *GroupCommand) ToApi() tgbotapi.BotCommand {
// The type is used to recognize commands and execute // The type is used to recognize commands and execute
// its actions and widgets . // its actions and widgets .
type CommandWidget struct { type CommandCompo struct {
PreStart Action PreStart Action
Commands CommandMap Commands CommandMap
Usage Action Usage Action
} }
// Returns new empty CommandWidget. // Returns new empty CommandCompo.
func NewCommandWidget() *CommandWidget { func NewCommandCompo() *CommandCompo {
ret := &CommandWidget{} ret := &CommandCompo{}
ret.Commands = make(CommandMap) ret.Commands = make(CommandMap)
return ret return ret
} }
// Set the commands to handle. // Set the commands to handle.
func (w *CommandWidget) WithCommands(cmds ...*Command) *CommandWidget { func (w *CommandCompo) WithCommands(cmds ...*Command) *CommandCompo {
for _, cmd := range cmds { for _, cmd := range cmds {
if cmd.Name == "" { if cmd.Name == "" {
panic("empty command name") panic("empty command name")
@ -129,41 +129,41 @@ func (w *CommandWidget) WithCommands(cmds ...*Command) *CommandWidget {
} }
// Set the prestart action. // Set the prestart action.
func (w *CommandWidget) WithPreStart(a Action) *CommandWidget { func (w *CommandCompo) WithPreStart(a Action) *CommandCompo {
w.PreStart = a w.PreStart = a
return w return w
} }
// Set the prestart action with function. // Set the prestart action with function.
func (w *CommandWidget) WithPreStartFunc(fn ActionFunc) *CommandWidget { func (w *CommandCompo) WithPreStartFunc(fn ActionFunc) *CommandCompo {
return w.WithPreStart(fn) return w.WithPreStart(fn)
} }
// Set the usage action. // Set the usage action.
func (w *CommandWidget) WithUsage(a Action) *CommandWidget { func (w *CommandCompo) WithUsage(a Action) *CommandCompo {
w.Usage = a w.Usage = a
return w return w
} }
// Set the usage action with function. // Set the usage action with function.
func (w *CommandWidget) WithUsageFunc(fn ActionFunc) *CommandWidget { func (w *CommandCompo) WithUsageFunc(fn ActionFunc) *CommandCompo {
return w.WithUsage(fn) return w.WithUsage(fn)
} }
func (widget *CommandWidget) Filter( func (widget *CommandCompo) Filter(
u *Update, u *Update,
msgs ...*Message,
) bool { ) bool {
/*if u.Message == nil || !u.Message.IsCommand() { if u.Message == nil || !u.Message.IsCommand() {
return false return false
}*/ }
return false return false
} }
func (widget *CommandWidget) Serve(c *Context) { // Implementing server.
func (compo *CommandCompo) Serve(c *Context) {
commanders := make(map[CommandName] BotCommander) commanders := make(map[CommandName] BotCommander)
for k, v := range widget.Commands { for k, v := range compo.Commands {
commanders[k] = v commanders[k] = v
} }
c.Bot.SetCommands( c.Bot.SetCommands(
@ -178,7 +178,7 @@ func (widget *CommandWidget) Serve(c *Context) {
// while we have the empty screen. // while we have the empty screen.
// E. g. the session did not start. // E. g. the session did not start.
if !(u.Message.IsCommand() && u.Message.Command() == "start") { if !(u.Message.IsCommand() && u.Message.Command() == "start") {
c.Run(widget.PreStart, u) c.WithUpdate(u).Run(compo.PreStart)
continue continue
} }
} }
@ -186,16 +186,16 @@ func (widget *CommandWidget) Serve(c *Context) {
if u.Message != nil && u.Message.IsCommand() { if u.Message != nil && u.Message.IsCommand() {
// Command handling. // Command handling.
cmdName := CommandName(u.Message.Command()) cmdName := CommandName(u.Message.Command())
cmd, ok := widget.Commands[cmdName] cmd, ok := compo.Commands[cmdName]
if !ok { if !ok {
c.Run(widget.Usage, u) c.WithUpdate(u).Run(compo.Usage)
continue continue
} }
c.Run(cmd.Action, u) c.WithUpdate(u).Run(cmd.Action)
if cmd.Widget != nil { if cmd.Widget != nil {
cmdUpdates.Close() cmdUpdates.Close()
cmdUpdates = c.runWidget(cmd.Widget) cmdUpdates = c.WithUpdate(u).RunWidget(cmd.Widget)
} }
continue continue
} }

View file

@ -9,7 +9,7 @@ type Widget interface {
// The way to describe custom function based Widgets. // The way to describe custom function based Widgets.
type RenderFunc func(c *Context) UI type RenderFunc func(c *Context) UI
func (fn RenderFunc) Uis(c *Context) UI { func (fn RenderFunc) Render(c *Context) UI {
return fn(c) return fn(c)
} }
@ -30,15 +30,18 @@ type Component interface {
} }
// The type to embed into potential components. // The type to embed into potential components.
// Implements empty versions of interfaces // Implements empty versions of interfaces.
// and contains
type Compo struct{ type Compo struct{
*Message *Message
} }
// Defalut setting message func NewCompo() *Compo {
func (compo Compo) SetMessage(msg *Message) { compo.Message = msg } return &Compo{}
func (compo Compo) GetMessage() *Message { return compo.Message } }
// Default non filtering filter. Always returns false.
func (compo Compo) Filter(_ *Update, _ *Message) bool {return false} // 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

@ -16,7 +16,7 @@ func (f Func) Act(c *Context) {
func (f Func) Serve(c *Context) { func (f Func) Serve(c *Context) {
f(c) f(c)
} }
func(f Func) Filter(_ *Context) bool { func(f Func) Filter(_ *Update) bool {
return false return false
} }
func (f Func) Render(_ *Context) UI { func (f Func) Render(_ *Context) UI {
@ -68,7 +68,7 @@ func (c *Context) PrevPath() Path {
func (c *Context) Run(a Action) { func (c *Context) Run(a Action) {
if a != nil { if a != nil {
a.Act(c.Copy().WithUpdate(u)) a.Act(c)
} }
} }
@ -186,52 +186,62 @@ func (c *Context) PathExist(pth Path) bool {
return c.Bot.behaviour.PathExist(pth) return c.Bot.behaviour.PathExist(pth)
} }
// Run widget in background returning the new input channel for it. func (c *Context) MakeArg(args []any) any {
func (c *Context) runWidget(widget Widget, args ...any) *UpdateChan {
if widget == nil {
return nil
}
var arg any var arg any
if len(args) == 1 { if len(args) == 1 {
arg = args[0] arg = args[0]
} else if len(args) > 1 { } else if len(args) > 1 {
arg = args arg = args
} }
return arg
}
pth := c.Path() func (c *Context) RunCompo(compo Component, args ...any) *UpdateChan {
uis := widget.UI() s, ok := compo.(Sendable)
// 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 { if ok {
msg := c.Send(s.SendConfig(c)) msg, err := c.Send(s)
ui.SetMessage(msg) if err != nil {
panic("could not send the message")
}
s.SetMessage(msg)
} }
updates := NewUpdateChan() updates := NewUpdateChan()
go func() { go func() {
ui.Serve( compo.Serve(
c.Copy(). c.WithInput(updates).
WithInput(updates). WithArg(c.MakeArg(args)),
WithArg(arg),
) )
// To let widgets finish themselves before // To let widgets finish themselves before
// the channel is closed and close it by themselves. // the channel is closed and close it by themselves.
updates.Close() updates.Close()
}() }()
chns[ui] = updates 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()
compos := widget.Render(c.WithArg(c.MakeArg(args)))
// Leave if changed path.
if pth != c.Path() {
return nil
}
chns := make([]*UpdateChan, len(compos))
for i, compo := range compos {
chns[i] = c.RunCompo(compo)
} }
ret := NewUpdateChan() ret := NewUpdateChan()
go func() { go func() {
for u := range ret { for u := range ret.Chan() {
for ui := range uis { for i, compo := range compos {
if !ui.Filter() { chn := chns[i]
chns[ui] <- u if !compo.Filter(u) {
chn.Send(u)
} }
} }
} }
@ -239,7 +249,7 @@ func (c *Context) runWidget(widget Widget, args ...any) *UpdateChan {
for _, chn := range chns { for _, chn := range chns {
chn.Close() chn.Close()
} }
} }()
return ret return ret
} }

View file

@ -23,15 +23,18 @@ var (
) )
type File struct { type File struct {
*Compo
path string path string
typ FileType typ FileType
caption string caption string
} }
func NewFile(path string) *File { func NewFile(path string) *File {
return &File{ ret := &File{
path: path, path: path,
} }
ret.Compo = NewCompo()
return ret
} }
func (f *File) withType(typ FileType) *File { func (f *File) withType(typ FileType) *File {
@ -73,14 +76,14 @@ func (f *File) SendData() string {
return "" return ""
} }
func (f *File) SendConfig( func (f *File) SendConfig(
sid SessionId, bot *Bot, c *Context,
) (*SendConfig) { ) (*SendConfig) {
var config SendConfig var config SendConfig
cid := sid.ToApi() sid := c.Session.Id.ToApi()
switch f.Type() { switch f.Type() {
case ImageFileType: case ImageFileType:
photo := tgbotapi.NewPhoto(cid, f) photo := tgbotapi.NewPhoto(sid, f)
photo.Caption = f.caption photo.Caption = f.caption
config.Image = &photo config.Image = &photo

View file

@ -10,9 +10,9 @@ type Filterer interface {
Filter(*Update) bool Filter(*Update) bool
} }
type FilterFunc func(*Update, MessageMap) bool type FilterFunc func(*Update) bool
func (f FilterFunc) Filter( func (f FilterFunc) Filter(
u *Update, msgs MessageMap, u *Update,
) bool { ) bool {
return f(u, msgs) return f(u)
} }

View file

@ -25,15 +25,16 @@ func (kbd *Inline) ToApi() tgbotapi.InlineKeyboardMarkup {
// Transform the keyboard to widget with the specified text. // Transform the keyboard to widget with the specified text.
func (kbd *Inline) Compo(text string) *InlineCompo { func (kbd *Inline) Compo(text string) *InlineCompo {
ret := &InlinCompo{} ret := &InlineCompo{}
ret.Inline = kbd ret.Inline = kbd
ret.Text = text ret.Text = text
ret.Compo = NewCompo()
return ret 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 *Compo
Text string Text string
*Inline *Inline
} }
@ -87,13 +88,13 @@ func (widget *InlineCompo) Serve(c *Context) {
} else if widget.Action != nil { } else if widget.Action != nil {
act = widget.Action act = widget.Action
} }
c.Run(act, u) c.WithUpdate(u).Run(act)
} }
} }
// Implementing the Filterer interface. // Implementing the Filterer interface.
func (compo *InlineCompo) Filter(u *Update) bool { func (compo *InlineCompo) Filter(u *Update) bool {
if widget == nil || u.CallbackQuery == nil { if compo == nil || u.CallbackQuery == nil {
return true return true
} }

View file

@ -6,6 +6,7 @@ import (
// Simple text message type. // Simple text message type.
type MessageConfig struct { type MessageConfig struct {
Compo
ParseMode string ParseMode string
Text string Text string
} }
@ -38,10 +39,10 @@ func (msg *MessageConfig) HTML() *MessageConfig {
} }
func (config *MessageConfig) SendConfig( func (config *MessageConfig) SendConfig(
sid SessionId, bot *Bot, c *Context,
) (*SendConfig) { ) (*SendConfig) {
var ret 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 = &msg
ret.Message.ParseMode = config.ParseMode ret.Message.ParseMode = config.ParseMode
return &ret return &ret

View file

@ -1,52 +1,3 @@
package tg 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
}
}

View file

@ -55,17 +55,19 @@ func (kbd *Reply) Compo(text string) *ReplyCompo {
ret := &ReplyCompo{} ret := &ReplyCompo{}
ret.Reply = kbd ret.Reply = kbd
ret.Text = text ret.Text = text
ret.Compo = NewCompo()
return ret return ret
} }
// The type implements reply keyboard widget. // The type implements reply keyboard widget.
type ReplyCompo struct { type ReplyCompo struct {
*Compo
Text string Text string
*Reply *Reply
} }
// Implementing the sendable interface. // Implementing the sendable interface.
func (compo *ReplyCompo) Render( func (compo *ReplyCompo) SendConfig(
c *Context, c *Context,
) (*SendConfig) { ) (*SendConfig) {
sid := c.Session.Id.ToApi() sid := c.Session.Id.ToApi()
@ -98,10 +100,10 @@ func (compo *ReplyCompo) Filter(
return true return true
} }
_, ok := widget.ButtonMap()[u.Message.Text] _, ok := compo.ButtonMap()[u.Message.Text]
if !ok { if !ok {
if u.Message.Location != nil { if u.Message.Location != nil {
locBtn := widget.ButtonMap().LocationButton() locBtn := compo.ButtonMap().LocationButton()
if locBtn == nil { if locBtn == nil {
return true return true
} }
@ -117,7 +119,7 @@ func (compo *ReplyCompo) Serve(c *Context) {
for u := range c.Input() { for u := range c.Input() {
var btn *Button var btn *Button
text := u.Message.Text text := u.Message.Text
btns := widget.ButtonMap() btns := compo.ButtonMap()
btn, ok := btns[text] btn, ok := btns[text]
if !ok { if !ok {
@ -127,7 +129,7 @@ func (compo *ReplyCompo) Serve(c *Context) {
} }
if btn != nil { if btn != nil {
c.Run(btn.Action, u) c.WithUpdate(u).Run(btn.Action)
} }
} }
} }

View file

@ -4,7 +4,7 @@ package tg
// related to. // related to.
type SessionScope uint8 type SessionScope uint8
const ( const (
NoSessionScope ContextScope = iota NoSessionScope SessionScope = iota
PrivateSessionScope PrivateSessionScope
GroupSessionScope GroupSessionScope
ChannelSessionScope ChannelSessionScope
@ -30,9 +30,10 @@ type Session struct {
} }
// Return new empty session with specified user ID. // Return new empty session with specified user ID.
func NewSession(id SessionId) *Session { func NewSession(id SessionId, scope SessionScope) *Session {
return &Session{ return &Session{
Id: id, Id: id,
Scope: scope,
} }
} }
@ -41,9 +42,9 @@ func NewSession(id SessionId) *Session {
type SessionMap map[SessionId]*Session type SessionMap map[SessionId]*Session
// Add new empty session by it's ID. // Add new empty session by it's ID.
func (sm SessionMap) Add(sid SessionId) *Session { func (sm SessionMap) Add(sid SessionId, scope SessionScope) *Session {
ret := NewSession(sid) ret := NewSession(sid, scope)
sm[sid] = NewSession(sid) sm[sid] = ret
return ret return ret
} }