Finally implemented at least not stable but working version.
This commit is contained in:
parent
abf080164a
commit
0aaabff503
13 changed files with 206 additions and 249 deletions
178
cmd/test/main.go
178
cmd/test/main.go
|
@ -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(
|
||||||
"The testing bot started!\n",
|
"Hello, %s!\n",
|
||||||
"You can see the basics of usage in the ",
|
"The testing bot started!\n",
|
||||||
"cmd/test/main.go file!",
|
"You can see the basics of usage in the ",
|
||||||
|
"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 {
|
||||||
tg.NewKeyboard().Row(
|
return tg.UI{
|
||||||
tg.NewButton("Upper case").Go("upper-case"),
|
tg.NewKeyboard().Row(
|
||||||
tg.NewButton("Lower case").Go("lower-case"),
|
tg.NewButton("Upper case").Go("upper-case"),
|
||||||
).Row(
|
tg.NewButton("Lower case").Go("lower-case"),
|
||||||
backButton,
|
).Row(
|
||||||
).Reply().Widget(
|
backButton,
|
||||||
"Choose the function to mutate string",
|
).Reply().Compo(
|
||||||
),
|
"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(
|
|
||||||
"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)
|
|
||||||
}),
|
}),
|
||||||
|
),
|
||||||
|
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(
|
tg.NewNode(
|
||||||
"send-location", tg.NewPage().WithReply(
|
"inc-dec", tg.RenderFunc(func(c *tg.Context) tg.UI {
|
||||||
sendLocationKeyboard.Widget("Press the button to send your location!"),
|
d := ExtractSessionData(c)
|
||||||
).WithInline(
|
return tg.UI{
|
||||||
tg.NewKeyboard().Row(
|
incDecKeyboard.Reply().Compo(fmt.Sprintf(
|
||||||
tg.NewButton(
|
"Press the buttons to increment and decrement.\n" +
|
||||||
"Check",
|
"Current counter value = %d", d.Counter,
|
||||||
).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"),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)).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").
|
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) {
|
||||||
|
|
19
tg/beh.go
19
tg/beh.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
20
tg/bot.go
20
tg/bot.go
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
19
tg/compo.go
19
tg/compo.go
|
@ -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}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
pth := c.Path()
|
||||||
uis := widget.UI()
|
compos := widget.Render(c.WithArg(c.MakeArg(args)))
|
||||||
// Leave if changed path.
|
// Leave if changed path.
|
||||||
if pth != c.Path() {
|
if pth != c.Path() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
chns := make(map[UI] *UpdateChan)
|
chns := make([]*UpdateChan, len(compos))
|
||||||
for _, ui := range uis {
|
for i, compo := range compos {
|
||||||
s, ok := ui.(Sendable)
|
chns[i] = c.RunCompo(compo)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
11
tg/file.go
11
tg/file.go
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
49
tg/read.go
49
tg/read.go
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
12
tg/reply.go
12
tg/reply.go
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue