Made the root Widget a separate entity.
This commit is contained in:
parent
36fa167549
commit
1d543bf444
6 changed files with 190 additions and 135 deletions
|
@ -110,9 +110,6 @@ var beh = tg.NewBehaviour().
|
||||||
// The session initialization.
|
// The session initialization.
|
||||||
c.Session.Data = &SessionData{}
|
c.Session.Data = &SessionData{}
|
||||||
|
|
||||||
}). // On any message update before the bot created session.
|
|
||||||
WithPreStartFunc(func(c *tg.Context){
|
|
||||||
c.Sendf("Please, use the /start command to start the bot")
|
|
||||||
}).WithScreens(
|
}).WithScreens(
|
||||||
tg.NewScreen("start", tg.NewPage(
|
tg.NewScreen("start", tg.NewPage(
|
||||||
"The bot started!",
|
"The bot started!",
|
||||||
|
@ -187,13 +184,17 @@ var beh = tg.NewBehaviour().
|
||||||
}),
|
}),
|
||||||
tg.NewCommand("read").
|
tg.NewCommand("read").
|
||||||
Desc("reads a string and sends it back").
|
Desc("reads a string and sends it back").
|
||||||
ActionFunc(func(c *tg.Context) {
|
WidgetFunc(func(c *tg.Context, updates chan *tg.Update) error {
|
||||||
/*c.Sendf("Type some text:")
|
c.Sendf("Type text and I will send it back to you")
|
||||||
msg, err := c.ReadTextMessage()
|
for u := range updates {
|
||||||
if err != nil {
|
if u.Message == nil {
|
||||||
return
|
continue
|
||||||
|
}
|
||||||
|
c.Sendf("You typed %q", u.Message.Text)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
c.Sendf("You typed %q", msg)*/
|
c.Sendf("Done")
|
||||||
|
return nil
|
||||||
}),
|
}),
|
||||||
tg.NewCommand("image").
|
tg.NewCommand("image").
|
||||||
Desc("sends a sample image").
|
Desc("sends a sample image").
|
||||||
|
|
47
tg/beh.go
47
tg/beh.go
|
@ -5,20 +5,24 @@ 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 {
|
||||||
PreStart *action
|
Root Widget
|
||||||
Init *action
|
Init *action
|
||||||
Screens ScreenMap
|
Screens ScreenMap
|
||||||
Commands CommandMap
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns new empty behaviour.
|
// Returns new empty behaviour.
|
||||||
func NewBehaviour() *Behaviour {
|
func NewBehaviour() *Behaviour {
|
||||||
return &Behaviour{
|
return &Behaviour{
|
||||||
Screens: make(ScreenMap),
|
Screens: make(ScreenMap),
|
||||||
Commands: make(CommandMap),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
@ -33,20 +37,6 @@ func (b *Behaviour) WithInitFunc(
|
||||||
return b.WithInit(fn)
|
return b.WithInit(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defines pre-start action.
|
|
||||||
// E. g. when the user has not type the "/start" command.
|
|
||||||
// Mostly used to send the "/start" command back
|
|
||||||
// with some warning.
|
|
||||||
func (b *Behaviour) WithPreStart(a Action) *Behaviour {
|
|
||||||
b.PreStart = newAction(a)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alias for WithPreStart to be used with function inside.
|
|
||||||
func (b *Behaviour) WithPreStartFunc(fn ActionFunc) *Behaviour {
|
|
||||||
return b.WithPreStart(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The function sets screens.
|
// The function sets screens.
|
||||||
func (b *Behaviour) WithScreens(
|
func (b *Behaviour) WithScreens(
|
||||||
screens ...*Screen,
|
screens ...*Screen,
|
||||||
|
@ -64,18 +54,17 @@ func (b *Behaviour) WithScreens(
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
// The function sets commands.
|
// The function sets as the standard root widget CommandWidget
|
||||||
|
// and its commands..
|
||||||
func (b *Behaviour) WithCommands(cmds ...*Command) *Behaviour {
|
func (b *Behaviour) WithCommands(cmds ...*Command) *Behaviour {
|
||||||
for _, cmd := range cmds {
|
b.Root = NewCommandWidget().
|
||||||
if cmd.Name == "" {
|
WithCommands(cmds...).
|
||||||
panic("empty command name")
|
WithPreStartFunc(func(c *Context){
|
||||||
}
|
c.Sendf("Please, use the /start command to start")
|
||||||
_, ok := b.Commands[cmd.Name]
|
}).WithUsageFunc(func(c *Context){
|
||||||
if ok {
|
c.Sendf("No such command")
|
||||||
panic("duplicate command definition")
|
})
|
||||||
}
|
|
||||||
b.Commands[cmd.Name] = cmd
|
|
||||||
}
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
17
tg/bot.go
17
tg/bot.go
|
@ -117,7 +117,7 @@ func (b *Bot) WithGroupSessions(sessions GroupSessionMap) *Bot {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setting the command on the user side.
|
// Setting the command on the user side.
|
||||||
func (bot *Bot) setCommands(
|
func (bot *Bot) SetCommands(
|
||||||
scope tgbotapi.BotCommandScope,
|
scope tgbotapi.BotCommandScope,
|
||||||
cmdMap map[CommandName] BotCommander,
|
cmdMap map[CommandName] BotCommander,
|
||||||
) {
|
) {
|
||||||
|
@ -156,20 +156,17 @@ func (bot *Bot) Run() error {
|
||||||
bot.groupBehaviour == nil {
|
bot.groupBehaviour == nil {
|
||||||
return errors.New("no behaviour defined")
|
return errors.New("no behaviour defined")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if bot.behaviour != nil && bot.behaviour.Root == nil {
|
||||||
|
return errors.New("the root widget is not set, cannot run")
|
||||||
|
}
|
||||||
|
|
||||||
uc := tgbotapi.NewUpdate(0)
|
uc := tgbotapi.NewUpdate(0)
|
||||||
uc.Timeout = 60
|
uc.Timeout = 60
|
||||||
updates := bot.Api.GetUpdatesChan(uc)
|
updates := bot.Api.GetUpdatesChan(uc)
|
||||||
handles := make(map[string]chan *Update)
|
handles := make(map[string]chan *Update)
|
||||||
|
|
||||||
if bot.behaviour != nil {
|
if bot.behaviour != nil {
|
||||||
commanders := make(map[CommandName] BotCommander)
|
|
||||||
for k, v := range bot.behaviour.Commands {
|
|
||||||
commanders[k] = v
|
|
||||||
}
|
|
||||||
bot.setCommands(
|
|
||||||
tgbotapi.NewBotCommandScopeAllPrivateChats(),
|
|
||||||
commanders,
|
|
||||||
)
|
|
||||||
chn := make(chan *Update)
|
chn := make(chan *Update)
|
||||||
handles["private"] = chn
|
handles["private"] = chn
|
||||||
go bot.handlePrivate(chn)
|
go bot.handlePrivate(chn)
|
||||||
|
@ -180,7 +177,7 @@ func (bot *Bot) Run() error {
|
||||||
for k, v := range bot.groupBehaviour.Commands {
|
for k, v := range bot.groupBehaviour.Commands {
|
||||||
commanders[k] = v
|
commanders[k] = v
|
||||||
}
|
}
|
||||||
bot.setCommands(
|
bot.SetCommands(
|
||||||
tgbotapi.NewBotCommandScopeAllGroupChats(),
|
tgbotapi.NewBotCommandScopeAllGroupChats(),
|
||||||
commanders,
|
commanders,
|
||||||
)
|
)
|
||||||
|
|
110
tg/command.go
110
tg/command.go
|
@ -89,3 +89,113 @@ func (c *GroupCommand) ToApi() tgbotapi.BotCommand {
|
||||||
ret.Description = c.Description
|
ret.Description = c.Description
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The type is used to recognize commands and execute
|
||||||
|
// its actions and widgets .
|
||||||
|
type CommandWidget struct {
|
||||||
|
PreStart Action
|
||||||
|
Commands CommandMap
|
||||||
|
Usage Action
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns new empty CommandWidget.
|
||||||
|
func NewCommandWidget() *CommandWidget {
|
||||||
|
ret := &CommandWidget{}
|
||||||
|
ret.Commands = make(CommandMap)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the commands to handle.
|
||||||
|
func (w *CommandWidget) WithCommands(cmds ...*Command) *CommandWidget {
|
||||||
|
for _, cmd := range cmds {
|
||||||
|
if cmd.Name == "" {
|
||||||
|
panic("empty command name")
|
||||||
|
}
|
||||||
|
_, ok := w.Commands[cmd.Name]
|
||||||
|
if ok {
|
||||||
|
panic("duplicate command definition")
|
||||||
|
}
|
||||||
|
w.Commands[cmd.Name] = cmd
|
||||||
|
}
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the prestart action.
|
||||||
|
func (w *CommandWidget) WithPreStart(a Action) *CommandWidget {
|
||||||
|
w.PreStart = a
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the prestart action with function.
|
||||||
|
func (w *CommandWidget) WithPreStartFunc(fn ActionFunc) *CommandWidget {
|
||||||
|
return w.WithPreStart(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the usage action.
|
||||||
|
func (w *CommandWidget) WithUsage(a Action) *CommandWidget {
|
||||||
|
w.Usage = a
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the usage action with function.
|
||||||
|
func (w *CommandWidget) WithUsageFunc(fn ActionFunc) *CommandWidget {
|
||||||
|
return w.WithUsage(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (widget *CommandWidget) Serve(c *Context, updates chan *Update) error {
|
||||||
|
commanders := make(map[CommandName] BotCommander)
|
||||||
|
for k, v := range widget.Commands {
|
||||||
|
commanders[k] = v
|
||||||
|
}
|
||||||
|
c.Bot.SetCommands(
|
||||||
|
tgbotapi.NewBotCommandScopeAllPrivateChats(),
|
||||||
|
commanders,
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdUpdates chan *Update
|
||||||
|
for u := range updates {
|
||||||
|
if c.ScreenId() == "" && u.Message != nil {
|
||||||
|
// Skipping and executing the preinit action
|
||||||
|
// while we have the empty screen.
|
||||||
|
// E. g. the session did not start.
|
||||||
|
if !(u.Message.IsCommand() && u.Message.Command() == "start") {
|
||||||
|
c.Run(widget.PreStart, u)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Message != nil && u.Message.IsCommand() {
|
||||||
|
// Command handling.
|
||||||
|
cmdName := CommandName(u.Message.Command())
|
||||||
|
cmd, ok := widget.Commands[cmdName]
|
||||||
|
if !ok {
|
||||||
|
c.Run(widget.Usage, u)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c.Run(cmd.Action, u)
|
||||||
|
if cmd.Widget != nil {
|
||||||
|
if cmdUpdates != nil {
|
||||||
|
close(cmdUpdates)
|
||||||
|
}
|
||||||
|
cmdUpdates = make(chan *Update)
|
||||||
|
go func() {
|
||||||
|
cmd.Widget.Serve(
|
||||||
|
&Context{context: c.context, Update: u},
|
||||||
|
cmdUpdates,
|
||||||
|
)
|
||||||
|
close(cmdUpdates)
|
||||||
|
cmdUpdates = nil
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmdUpdates != nil {
|
||||||
|
// Send to the commands channel if we are
|
||||||
|
// executing one.
|
||||||
|
cmdUpdates <- u
|
||||||
|
} else {
|
||||||
|
c.Skip(u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -141,9 +141,7 @@ func (p *Page) Serve(
|
||||||
act = kbd.Action
|
act = kbd.Action
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if act != nil {
|
c.Run(act, u)
|
||||||
c.run(act, u)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
128
tg/private.go
128
tg/private.go
|
@ -10,100 +10,67 @@ type context struct {
|
||||||
Session *Session
|
Session *Session
|
||||||
// To reach the bot abilities inside callbacks.
|
// To reach the bot abilities inside callbacks.
|
||||||
Bot *Bot
|
Bot *Bot
|
||||||
widgetUpdates chan *Update
|
skippedUpdates chan *Update
|
||||||
curScreen, prevScreen *Screen
|
// Current screen ID.
|
||||||
|
screenId, prevScreenId ScreenId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// The type represents way to interact with user in
|
// The type represents way to interact with user in
|
||||||
// handling functions. Is provided to Act() function always.
|
// handling functions. Is provided to Act() function always.
|
||||||
|
|
||||||
// Goroutie function to handle each user.
|
// Goroutie function to handle each user.
|
||||||
func (c *context) handleUpdateChan(updates chan *Update) {
|
func (c *context) handleUpdateChan(updates chan *Update) {
|
||||||
beh := c.Bot.behaviour
|
beh := c.Bot.behaviour
|
||||||
|
|
||||||
session := c.Session
|
|
||||||
preStart := beh.PreStart
|
|
||||||
if beh.Init != nil {
|
if beh.Init != nil {
|
||||||
c.run(beh.Init, nil)
|
c.run(beh.Init, nil)
|
||||||
}
|
}
|
||||||
var cmdUpdates chan *Update
|
beh.Root.Serve(&Context{
|
||||||
for u := range updates {
|
context: c,
|
||||||
// The part is added to implement custom update handling.
|
}, updates)
|
||||||
if !session.started {
|
|
||||||
if u.Message.IsCommand() &&
|
|
||||||
u.Message.Command() == "start" {
|
|
||||||
// Special treatment for the "/start"
|
|
||||||
// command.
|
|
||||||
session.started = true
|
|
||||||
cmdName := CommandName("start")
|
|
||||||
cmd, ok := beh.Commands[cmdName]
|
|
||||||
if ok {
|
|
||||||
if cmd.Action != nil {
|
|
||||||
c.run(cmd.Action, u)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Some usage.
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Prestart handling.
|
|
||||||
c.run(preStart, u)
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.Message != nil && u.Message.IsCommand() {
|
|
||||||
// Command handling.
|
|
||||||
cmdName := CommandName(u.Message.Command())
|
|
||||||
cmd, ok := beh.Commands[cmdName]
|
|
||||||
if ok {
|
|
||||||
if cmd.Action != nil {
|
|
||||||
c.run(cmd.Action, u)
|
|
||||||
}
|
|
||||||
if cmd.Widget != nil {
|
|
||||||
if cmdUpdates != nil {
|
|
||||||
close(cmdUpdates)
|
|
||||||
}
|
|
||||||
cmdUpdates = make(chan *Update)
|
|
||||||
go func() {
|
|
||||||
cmd.Widget.Serve(
|
|
||||||
&Context{context: c, Update: u},
|
|
||||||
cmdUpdates,
|
|
||||||
)
|
|
||||||
close(cmdUpdates)
|
|
||||||
cmdUpdates = nil
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Some usage.
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// The standard thing - send messages to widgets.
|
|
||||||
if cmdUpdates != nil {
|
|
||||||
cmdUpdates <- u
|
|
||||||
} else {
|
|
||||||
c.widgetUpdates <- u
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (c *context) run(a Action, u *Update) {
|
func (c *context) run(a Action, u *Update) {
|
||||||
a.Act(&Context{context: c, Update: u})
|
a.Act(&Context{context: c, Update: u})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) Render(v Renderable) ([]*Message, error) {
|
func (c *Context) ScreenId() ScreenId {
|
||||||
|
return c.screenId
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) PrevScreenId() ScreenId {
|
||||||
|
return c.prevScreenId
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Run(a Action, u *Update) {
|
||||||
|
if a != nil {
|
||||||
|
a.Act(&Context{context: c.context, Update: u})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only for the root widget usage.
|
||||||
|
// Skip the update sending it down to
|
||||||
|
// the underlying widget.
|
||||||
|
func (c *Context) Skip(u *Update) {
|
||||||
|
if c.skippedUpdates != nil {
|
||||||
|
c.skippedUpdates <- u
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renders the Renedrable object to the side of client
|
||||||
|
// and returns the messages it sent.
|
||||||
|
func (c *Context) Render(v Renderable) ([]*Message, error) {
|
||||||
return c.Bot.Render(c.Session.Id, v)
|
return c.Bot.Render(c.Session.Id, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sends to the Sendable object.
|
// Sends to the Sendable object.
|
||||||
func (c *context) Send(v Sendable) (*Message, error) {
|
func (c *Context) Send(v Sendable) (*Message, error) {
|
||||||
return c.Bot.Send(c.Session.Id, v)
|
return c.Bot.Send(c.Session.Id, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sends the formatted with fmt.Sprintf message to the user.
|
// Sends the formatted with fmt.Sprintf message to the user.
|
||||||
func (c *context) Sendf(format string, v ...any) (*Message, error) {
|
func (c *Context) Sendf(format string, v ...any) (*Message, error) {
|
||||||
msg, err := c.Send(NewMessage(
|
msg, err := c.Send(NewMessage(
|
||||||
c.Session.Id, fmt.Sprintf(format, v...),
|
c.Session.Id, fmt.Sprintf(format, v...),
|
||||||
))
|
))
|
||||||
|
@ -165,29 +132,22 @@ func (c *Context) ChangeScreen(screenId ScreenId) error {
|
||||||
// Getting the screen and changing to
|
// Getting the screen and changing to
|
||||||
// then executing its widget.
|
// then executing its widget.
|
||||||
screen := c.Bot.behaviour.Screens[screenId]
|
screen := c.Bot.behaviour.Screens[screenId]
|
||||||
c.prevScreen = c.curScreen
|
c.prevScreenId = c.screenId
|
||||||
c.curScreen = screen
|
c.screenId = screenId
|
||||||
|
|
||||||
// Making the new channel for the widget.
|
// Making the new channel for the widget.
|
||||||
if c.widgetUpdates != nil {
|
if c.skippedUpdates != nil {
|
||||||
close(c.widgetUpdates)
|
close(c.skippedUpdates)
|
||||||
}
|
}
|
||||||
c.widgetUpdates = make(chan *Update)
|
c.skippedUpdates = make(chan *Update)
|
||||||
if screen.Widget != nil {
|
if screen.Widget != nil {
|
||||||
// Running the widget if the screen has one.
|
// Running the widget if the screen has one.
|
||||||
go screen.Widget.Serve(c, c.widgetUpdates)
|
|
||||||
} else {
|
|
||||||
// Skipping updates if there is no
|
|
||||||
// widget to handle them.
|
|
||||||
go func() {
|
go func() {
|
||||||
for _ = range c.widgetUpdates {}
|
screen.Widget.Serve(c, c.skippedUpdates)
|
||||||
}()
|
}()
|
||||||
|
} else {
|
||||||
|
panic("no widget defined for the screen")
|
||||||
}
|
}
|
||||||
|
|
||||||
//c.Bot.Render(c.Session.Id, screen)
|
|
||||||
//if screen.Action != nil {
|
|
||||||
//c.run(screen.Action, c.Update)
|
|
||||||
//}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue