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.
|
||||
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(
|
||||
tg.NewScreen("start", tg.NewPage(
|
||||
"The bot started!",
|
||||
|
@ -187,13 +184,17 @@ var beh = tg.NewBehaviour().
|
|||
}),
|
||||
tg.NewCommand("read").
|
||||
Desc("reads a string and sends it back").
|
||||
ActionFunc(func(c *tg.Context) {
|
||||
/*c.Sendf("Type some text:")
|
||||
msg, err := c.ReadTextMessage()
|
||||
if err != nil {
|
||||
return
|
||||
WidgetFunc(func(c *tg.Context, updates chan *tg.Update) error {
|
||||
c.Sendf("Type text and I will send it back to you")
|
||||
for u := range updates {
|
||||
if u.Message == nil {
|
||||
continue
|
||||
}
|
||||
c.Sendf("You typed %q", u.Message.Text)
|
||||
break
|
||||
}
|
||||
c.Sendf("You typed %q", msg)*/
|
||||
c.Sendf("Done")
|
||||
return nil
|
||||
}),
|
||||
tg.NewCommand("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.
|
||||
type Behaviour struct {
|
||||
PreStart *action
|
||||
Root Widget
|
||||
Init *action
|
||||
Screens ScreenMap
|
||||
Commands CommandMap
|
||||
}
|
||||
|
||||
// Returns new empty behaviour.
|
||||
func NewBehaviour() *Behaviour {
|
||||
return &Behaviour{
|
||||
Screens: make(ScreenMap),
|
||||
Commands: make(CommandMap),
|
||||
Screens: make(ScreenMap),
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
// not when starting or restarting the bot with the Start Action.
|
||||
func (b *Behaviour) WithInit(a Action) *Behaviour {
|
||||
|
@ -33,20 +37,6 @@ func (b *Behaviour) WithInitFunc(
|
|||
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.
|
||||
func (b *Behaviour) WithScreens(
|
||||
screens ...*Screen,
|
||||
|
@ -64,18 +54,17 @@ func (b *Behaviour) WithScreens(
|
|||
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 {
|
||||
for _, cmd := range cmds {
|
||||
if cmd.Name == "" {
|
||||
panic("empty command name")
|
||||
}
|
||||
_, ok := b.Commands[cmd.Name]
|
||||
if ok {
|
||||
panic("duplicate command definition")
|
||||
}
|
||||
b.Commands[cmd.Name] = cmd
|
||||
}
|
||||
b.Root = NewCommandWidget().
|
||||
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
|
||||
}
|
||||
|
||||
|
|
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.
|
||||
func (bot *Bot) setCommands(
|
||||
func (bot *Bot) SetCommands(
|
||||
scope tgbotapi.BotCommandScope,
|
||||
cmdMap map[CommandName] BotCommander,
|
||||
) {
|
||||
|
@ -156,20 +156,17 @@ func (bot *Bot) Run() error {
|
|||
bot.groupBehaviour == nil {
|
||||
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.Timeout = 60
|
||||
updates := bot.Api.GetUpdatesChan(uc)
|
||||
handles := make(map[string]chan *Update)
|
||||
|
||||
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)
|
||||
handles["private"] = chn
|
||||
go bot.handlePrivate(chn)
|
||||
|
@ -180,7 +177,7 @@ func (bot *Bot) Run() error {
|
|||
for k, v := range bot.groupBehaviour.Commands {
|
||||
commanders[k] = v
|
||||
}
|
||||
bot.setCommands(
|
||||
bot.SetCommands(
|
||||
tgbotapi.NewBotCommandScopeAllGroupChats(),
|
||||
commanders,
|
||||
)
|
||||
|
|
110
tg/command.go
110
tg/command.go
|
@ -89,3 +89,113 @@ func (c *GroupCommand) ToApi() tgbotapi.BotCommand {
|
|||
ret.Description = c.Description
|
||||
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
|
||||
}
|
||||
}
|
||||
if act != nil {
|
||||
c.run(act, u)
|
||||
}
|
||||
c.Run(act, u)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
128
tg/private.go
128
tg/private.go
|
@ -10,100 +10,67 @@ type context struct {
|
|||
Session *Session
|
||||
// To reach the bot abilities inside callbacks.
|
||||
Bot *Bot
|
||||
widgetUpdates chan *Update
|
||||
curScreen, prevScreen *Screen
|
||||
skippedUpdates chan *Update
|
||||
// Current screen ID.
|
||||
screenId, prevScreenId ScreenId
|
||||
}
|
||||
|
||||
|
||||
// The type represents way to interact with user in
|
||||
// handling functions. Is provided to Act() function always.
|
||||
|
||||
// Goroutie function to handle each user.
|
||||
func (c *context) handleUpdateChan(updates chan *Update) {
|
||||
beh := c.Bot.behaviour
|
||||
|
||||
session := c.Session
|
||||
preStart := beh.PreStart
|
||||
if beh.Init != nil {
|
||||
c.run(beh.Init, nil)
|
||||
}
|
||||
var cmdUpdates chan *Update
|
||||
for u := range updates {
|
||||
// The part is added to implement custom update handling.
|
||||
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
|
||||
}
|
||||
}
|
||||
beh.Root.Serve(&Context{
|
||||
context: c,
|
||||
}, updates)
|
||||
}
|
||||
|
||||
|
||||
func (c *context) run(a Action, u *Update) {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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(
|
||||
c.Session.Id, fmt.Sprintf(format, v...),
|
||||
))
|
||||
|
@ -165,29 +132,22 @@ func (c *Context) ChangeScreen(screenId ScreenId) error {
|
|||
// Getting the screen and changing to
|
||||
// then executing its widget.
|
||||
screen := c.Bot.behaviour.Screens[screenId]
|
||||
c.prevScreen = c.curScreen
|
||||
c.curScreen = screen
|
||||
c.prevScreenId = c.screenId
|
||||
c.screenId = screenId
|
||||
|
||||
// Making the new channel for the widget.
|
||||
if c.widgetUpdates != nil {
|
||||
close(c.widgetUpdates)
|
||||
if c.skippedUpdates != nil {
|
||||
close(c.skippedUpdates)
|
||||
}
|
||||
c.widgetUpdates = make(chan *Update)
|
||||
c.skippedUpdates = make(chan *Update)
|
||||
if screen.Widget != nil {
|
||||
// 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() {
|
||||
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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue