Great refactoring and rethinking of the Context.
This commit is contained in:
parent
d99ea68198
commit
1fc2acbbd6
7 changed files with 157 additions and 90 deletions
|
@ -26,11 +26,14 @@ func NewMutateMessageWidget(fn func(string) string) *MutateMessageWidget {
|
|||
return ret
|
||||
}
|
||||
|
||||
func (w *MutateMessageWidget) Serve(c *tg.Context, updates *tg.UpdateChan) {
|
||||
for _, arg := range c.Args {
|
||||
func (w *MutateMessageWidget) Serve(c *tg.Context) {
|
||||
args, ok := c.Arg.([]any)
|
||||
if ok {
|
||||
for _, arg := range args {
|
||||
c.Sendf("%v", arg)
|
||||
}
|
||||
for u := range updates.Chan() {
|
||||
}
|
||||
for u := range c.Input() {
|
||||
text := u.Message.Text
|
||||
c.Sendf("%s", w.Mutate(text))
|
||||
}
|
||||
|
@ -49,7 +52,7 @@ func ExtractSessionData(c *tg.Context) *SessionData {
|
|||
|
||||
var (
|
||||
startScreenButton = tg.NewButton("🏠 To the start screen").
|
||||
ScreenChange("start")
|
||||
ScreenChange("/start")
|
||||
|
||||
incDecKeyboard = tg.NewKeyboard().Row(
|
||||
tg.NewButton("+").ActionFunc(func(c *tg.Context) {
|
||||
|
@ -67,14 +70,14 @@ var (
|
|||
)
|
||||
|
||||
navKeyboard = tg.NewKeyboard().Row(
|
||||
tg.NewButton("Inc/Dec").ScreenChange("start/inc-dec"),
|
||||
tg.NewButton("Inc/Dec").ScreenChange("/start/inc-dec"),
|
||||
).Row(
|
||||
tg.NewButton("Upper case").ActionFunc(func(c *tg.Context){
|
||||
c.ChangeScreen("start/upper-case", "this shit", "works")
|
||||
c.ChangeScreen("/start/upper-case", "this shit", "works")
|
||||
}),
|
||||
tg.NewButton("Lower case").ScreenChange("start/lower-case"),
|
||||
tg.NewButton("Lower case").ScreenChange("/start/lower-case"),
|
||||
).Row(
|
||||
tg.NewButton("Send location").ScreenChange("start/send-location"),
|
||||
tg.NewButton("Send location").ScreenChange("/start/send-location"),
|
||||
).Reply().WithOneTime(true)
|
||||
|
||||
sendLocationKeyboard = tg.NewKeyboard().Row(
|
||||
|
@ -107,7 +110,7 @@ var beh = tg.NewBehaviour().
|
|||
// The session initialization.
|
||||
c.Session.Data = &SessionData{}
|
||||
}).WithScreens(
|
||||
tg.NewScreen("start", tg.NewPage(
|
||||
tg.NewScreen("/start", tg.NewPage(
|
||||
"",
|
||||
).WithInline(
|
||||
tg.NewKeyboard().Row(
|
||||
|
@ -118,7 +121,7 @@ var beh = tg.NewBehaviour().
|
|||
navKeyboard.Widget("Choose what you are interested in"),
|
||||
),
|
||||
),
|
||||
tg.NewScreen("start/inc-dec", tg.NewPage(
|
||||
tg.NewScreen("/start/inc-dec", tg.NewPage(
|
||||
"The screen shows how "+
|
||||
"user separated data works "+
|
||||
"by saving the counter for each of users "+
|
||||
|
@ -132,7 +135,7 @@ var beh = tg.NewBehaviour().
|
|||
}),
|
||||
),
|
||||
|
||||
tg.NewScreen("start/upper-case", tg.NewPage(
|
||||
tg.NewScreen("/start/upper-case", tg.NewPage(
|
||||
"Type text and the bot will send you the upper case version to you",
|
||||
).WithReply(
|
||||
navToStartKeyboard.Widget(""),
|
||||
|
@ -141,7 +144,7 @@ var beh = tg.NewBehaviour().
|
|||
),
|
||||
),
|
||||
|
||||
tg.NewScreen("start/lower-case", tg.NewPage(
|
||||
tg.NewScreen("/start/lower-case", tg.NewPage(
|
||||
"Type text and the bot will send you the lower case version",
|
||||
).WithReply(
|
||||
navToStartKeyboard.Widget(""),
|
||||
|
@ -150,7 +153,7 @@ var beh = tg.NewBehaviour().
|
|||
),
|
||||
),
|
||||
|
||||
tg.NewScreen("start/send-location", tg.NewPage(
|
||||
tg.NewScreen("/start/send-location", tg.NewPage(
|
||||
"",
|
||||
).WithReply(
|
||||
sendLocationKeyboard.Widget("Press the button to send your location!"),
|
||||
|
@ -172,7 +175,7 @@ var beh = tg.NewBehaviour().
|
|||
Desc("start or restart the bot or move to the start screen").
|
||||
ActionFunc(func(c *tg.Context){
|
||||
c.Sendf("Your username is %q", c.Message.From.UserName)
|
||||
c.ChangeScreen("start")
|
||||
c.ChangeScreen("/start")
|
||||
}),
|
||||
tg.NewCommand("hello").
|
||||
Desc("sends the 'Hello, World!' message back").
|
||||
|
@ -181,9 +184,9 @@ var beh = tg.NewBehaviour().
|
|||
}),
|
||||
tg.NewCommand("read").
|
||||
Desc("reads a string and sends it back").
|
||||
WidgetFunc(func(c *tg.Context, updates *tg.UpdateChan) {
|
||||
WidgetFunc(func(c *tg.Context) {
|
||||
c.Sendf("Type text and I will send it back to you")
|
||||
for u := range updates.Chan() {
|
||||
for u := range c.Input() {
|
||||
if u.Message == nil {
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -233,7 +233,8 @@ func (bot *Bot) handlePrivate(updates chan *Update) {
|
|||
go (&Context{
|
||||
context: ctx,
|
||||
Update: u,
|
||||
}).Serve(chn)
|
||||
input: chn,
|
||||
}).serve()
|
||||
}
|
||||
} else if u.Message != nil {
|
||||
// Create session on any message
|
||||
|
@ -249,7 +250,8 @@ func (bot *Bot) handlePrivate(updates chan *Update) {
|
|||
go (&Context{
|
||||
context: ctx,
|
||||
Update: u,
|
||||
}).Serve(chn)
|
||||
input: chn,
|
||||
}).serve()
|
||||
}
|
||||
|
||||
chn, ok := chans[sid]
|
||||
|
|
|
@ -153,10 +153,7 @@ func (widget *Command) Filter(
|
|||
return false
|
||||
}
|
||||
|
||||
func (widget *CommandWidget) Serve(
|
||||
c *Context,
|
||||
updates *UpdateChan,
|
||||
) {
|
||||
func (widget *CommandWidget) Serve(c *Context) {
|
||||
commanders := make(map[CommandName] BotCommander)
|
||||
for k, v := range widget.Commands {
|
||||
commanders[k] = v
|
||||
|
@ -167,7 +164,7 @@ func (widget *CommandWidget) Serve(
|
|||
)
|
||||
|
||||
var cmdUpdates *UpdateChan
|
||||
for u := range updates.Chan() {
|
||||
for u := range c.Input() {
|
||||
if c.ScreenId() == "" && u.Message != nil {
|
||||
// Skipping and executing the preinit action
|
||||
// while we have the empty screen.
|
||||
|
@ -190,20 +187,12 @@ func (widget *CommandWidget) Serve(
|
|||
c.Run(cmd.Action, u)
|
||||
if cmd.Widget != nil {
|
||||
cmdUpdates.Close()
|
||||
cmdUpdates = NewUpdateChan()
|
||||
go func() {
|
||||
cmd.Widget.Serve(
|
||||
&Context{context: c.context, Update: u},
|
||||
cmdUpdates,
|
||||
)
|
||||
cmdUpdates.Close()
|
||||
cmdUpdates = nil
|
||||
}()
|
||||
cmdUpdates = c.RunWidget(cmd.Widget)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if cmdUpdates != nil {
|
||||
if !cmdUpdates.Closed() {
|
||||
// Send to the commands channel if we are
|
||||
// executing one.
|
||||
cmdUpdates.Send(u)
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
// Simple text message type.
|
||||
type MessageConfig struct {
|
||||
ParseMode string
|
||||
Text string
|
||||
}
|
||||
|
||||
|
@ -13,14 +14,35 @@ type MessageConfig struct {
|
|||
func NewMessage(text string) *MessageConfig {
|
||||
ret := &MessageConfig{}
|
||||
ret.Text = text
|
||||
ret.ParseMode = tgbotapi.ModeMarkdown
|
||||
return ret
|
||||
}
|
||||
|
||||
func (msg *MessageConfig) withParseMode(mode string) *MessageConfig{
|
||||
msg.ParseMode = mode
|
||||
return msg
|
||||
}
|
||||
|
||||
// Set the default Markdown parsing mode.
|
||||
func (msg *MessageConfig) MD() *MessageConfig {
|
||||
return msg.withParseMode(tgbotapi.ModeMarkdown)
|
||||
}
|
||||
|
||||
func (msg *MessageConfig) MD2() *MessageConfig {
|
||||
return msg.withParseMode(tgbotapi.ModeMarkdownV2)
|
||||
}
|
||||
|
||||
|
||||
func (msg *MessageConfig) HTML() *MessageConfig {
|
||||
return msg.withParseMode(tgbotapi.ModeHTML)
|
||||
}
|
||||
|
||||
func (config *MessageConfig) SendConfig(
|
||||
sid SessionId, bot *Bot,
|
||||
) (*SendConfig) {
|
||||
var ret SendConfig
|
||||
msg := tgbotapi.NewMessage(sid.ToApi(), config.Text)
|
||||
ret.Message = &msg
|
||||
ret.Message.ParseMode = config.ParseMode
|
||||
return &ret
|
||||
}
|
||||
|
|
16
tg/page.go
16
tg/page.go
|
@ -85,26 +85,24 @@ func (p *Page) Filter(
|
|||
return false
|
||||
}
|
||||
|
||||
func (p *Page) Serve(
|
||||
c *Context, updates *UpdateChan,
|
||||
) {
|
||||
msgs, _ := c.Render(p)
|
||||
inlineMsg := msgs["page/inline"]
|
||||
func (p *Page) Serve(c *Context) {
|
||||
if p.Action != nil {
|
||||
c.Run(p.Action, c.Update)
|
||||
}
|
||||
msgs, _ := c.Render(p)
|
||||
inlineMsg := msgs["page/inline"]
|
||||
|
||||
subUpdates := c.RunWidgetBg(p.SubWidget)
|
||||
subUpdates := c.RunWidget(p.SubWidget)
|
||||
defer subUpdates.Close()
|
||||
|
||||
inlineUpdates := c.RunWidgetBg(p.Inline)
|
||||
inlineUpdates := c.RunWidget(p.Inline)
|
||||
defer inlineUpdates.Close()
|
||||
|
||||
replyUpdates := c.RunWidgetBg(p.Reply)
|
||||
replyUpdates := c.RunWidget(p.Reply)
|
||||
defer replyUpdates.Close()
|
||||
|
||||
subFilter, subFilterOk := p.SubWidget.(Filterer)
|
||||
for u := range updates.Chan() {
|
||||
for u := range c.Input() {
|
||||
switch {
|
||||
case !p.Inline.Filter(u, MessageMap{"": inlineMsg}) :
|
||||
inlineUpdates.Send(u)
|
||||
|
|
105
tg/private.go
105
tg/private.go
|
@ -6,6 +6,18 @@ import (
|
|||
//tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
)
|
||||
|
||||
type ContextType string
|
||||
|
||||
const (
|
||||
NoContextType = iota
|
||||
PrivateContextType
|
||||
GroupContextType
|
||||
ChannelContextType
|
||||
)
|
||||
|
||||
// General context for a specific user.
|
||||
// Is always the same and is not reached
|
||||
// inside end function-handlers.
|
||||
type context struct {
|
||||
Session *Session
|
||||
// To reach the bot abilities inside callbacks.
|
||||
|
@ -15,17 +27,13 @@ type context struct {
|
|||
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) Serve(updates *UpdateChan) {
|
||||
func (c *Context) serve() {
|
||||
beh := c.Bot.behaviour
|
||||
if beh.Init != nil {
|
||||
c.Run(beh.Init, c.Update)
|
||||
}
|
||||
beh.Root.Serve(c, updates)
|
||||
beh.Root.Serve(c)
|
||||
}
|
||||
|
||||
|
||||
|
@ -70,6 +78,14 @@ func (c *Context) Sendf(format string, v ...any) (*Message, error) {
|
|||
return c.Send(NewMessage(fmt.Sprintf(format, v...)))
|
||||
}
|
||||
|
||||
func (c *Context) Sendf2(format string, v ...any) (*Message, error) {
|
||||
return c.Send(NewMessage(fmt.Sprintf(format, v...)).MD2())
|
||||
}
|
||||
|
||||
func (c *Context) SendfHTML(format string, v ...any) (*Message, error) {
|
||||
return c.Send(NewMessage(fmt.Sprintf(format, v...)).HTML())
|
||||
}
|
||||
|
||||
// Interface to interact with the user.
|
||||
type Context struct {
|
||||
*context
|
||||
|
@ -78,9 +94,43 @@ type Context struct {
|
|||
// Used as way to provide outer values redirection
|
||||
// into widgets and actions. It is like arguments
|
||||
// for REST API request etc.
|
||||
Args []any
|
||||
Arg any
|
||||
// Instead of updates as argument.
|
||||
input *UpdateChan
|
||||
}
|
||||
|
||||
// Get the input for current widget.
|
||||
// Should be used inside handlers (aka "Serve").
|
||||
func (c *Context) Input() chan *Update {
|
||||
return c.input.Chan()
|
||||
}
|
||||
|
||||
// Returns copy of current context so
|
||||
// it will not affect the current one.
|
||||
// But be careful because
|
||||
// most of the insides uses pointers
|
||||
// which are not deeply copied.
|
||||
func (c *Context) Copy() *Context {
|
||||
ret := *c
|
||||
return &ret
|
||||
}
|
||||
|
||||
func (c *Context) WithArg(v any) *Context {
|
||||
c.Arg = v
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Context) WithUpdate(u *Update) *Context {
|
||||
c.Update = u
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Context) WithInput(input *UpdateChan) *Context {
|
||||
c.input = input
|
||||
return c
|
||||
}
|
||||
|
||||
|
||||
// Customized actions for the bot.
|
||||
type Action interface {
|
||||
Act(*Context)
|
||||
|
@ -121,19 +171,9 @@ func (c *Context) ChangeScreen(screenId ScreenId, args ...any) error {
|
|||
|
||||
// Stopping the current widget.
|
||||
c.skippedUpdates.Close()
|
||||
// Making channel for the new widget.
|
||||
c.skippedUpdates = NewUpdateChan()
|
||||
c.skippedUpdates = nil
|
||||
if screen.Widget != nil {
|
||||
// Running the widget if the screen has one.
|
||||
go func() {
|
||||
updates := c.skippedUpdates
|
||||
screen.Widget.Serve(&Context{
|
||||
context: c.context,
|
||||
Update: c.Update,
|
||||
Args: args,
|
||||
}, updates)
|
||||
updates.Close()
|
||||
}()
|
||||
c.skippedUpdates = c.RunWidget(screen.Widget, args)
|
||||
} else {
|
||||
panic("no widget defined for the screen")
|
||||
}
|
||||
|
@ -141,6 +181,33 @@ func (c *Context) ChangeScreen(screenId ScreenId, args ...any) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
|
||||
var arg any
|
||||
if len(args) == 1 {
|
||||
arg = args[0]
|
||||
} else if len(args) > 1 {
|
||||
arg = args
|
||||
}
|
||||
|
||||
updates := NewUpdateChan()
|
||||
go func() {
|
||||
widget.Serve(
|
||||
c.Copy().
|
||||
WithInput(updates).
|
||||
WithArg(arg),
|
||||
)
|
||||
updates.Close()
|
||||
}()
|
||||
|
||||
return updates
|
||||
}
|
||||
|
||||
func (c *Context) ChangeToPrevScreen() {
|
||||
c.ChangeScreen(c.PrevScreenId())
|
||||
}
|
||||
|
|
42
tg/widget.go
42
tg/widget.go
|
@ -13,7 +13,7 @@ type Widget interface {
|
|||
// widget MUST end its work.
|
||||
// Mostly made by looping over the
|
||||
// updates range.
|
||||
Serve(*Context, *UpdateChan)
|
||||
Serve(*Context)
|
||||
}
|
||||
|
||||
// Needs implementation.
|
||||
|
@ -49,11 +49,13 @@ func (updates *UpdateChan) Chan() chan *Update {
|
|||
}
|
||||
|
||||
// Send an update to the channel.
|
||||
func (updates *UpdateChan) Send(u *Update) {
|
||||
if updates != nil && updates.chn == nil {
|
||||
return
|
||||
// Returns true if the update was sent.
|
||||
func (updates *UpdateChan) Send(u *Update) bool {
|
||||
if updates == nil || updates.chn == nil {
|
||||
return false
|
||||
}
|
||||
updates.chn <- u
|
||||
return true
|
||||
}
|
||||
|
||||
// Read an update from the channel.
|
||||
|
@ -66,7 +68,7 @@ func (updates *UpdateChan) Read() *Update {
|
|||
|
||||
// Returns true if the channel is closed.
|
||||
func (updates *UpdateChan) Closed() bool {
|
||||
return updates.chn == nil
|
||||
return updates==nil || updates.chn == nil
|
||||
}
|
||||
|
||||
// Close the channel. Used in defers.
|
||||
|
@ -78,16 +80,6 @@ func (updates *UpdateChan) Close() {
|
|||
updates.chn = nil
|
||||
}
|
||||
|
||||
func (c *Context) RunWidgetBg(widget Widget) *UpdateChan {
|
||||
if widget == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
updates := NewUpdateChan()
|
||||
go widget.Serve(c, updates)
|
||||
|
||||
return updates
|
||||
}
|
||||
|
||||
// Implementing the interface provides
|
||||
type DynamicWidget interface {
|
||||
|
@ -96,10 +88,10 @@ type DynamicWidget interface {
|
|||
|
||||
// The function that implements the Widget
|
||||
// interface.
|
||||
type WidgetFunc func(*Context, *UpdateChan)
|
||||
type WidgetFunc func(*Context)
|
||||
|
||||
func (wf WidgetFunc) Serve(c *Context, updates *UpdateChan) {
|
||||
wf(c, updates)
|
||||
func (wf WidgetFunc) Serve(c *Context) {
|
||||
wf(c)
|
||||
}
|
||||
|
||||
func (wf WidgetFunc) Filter(
|
||||
|
@ -145,11 +137,8 @@ func (widget *InlineKeyboardWidget) SendConfig(
|
|||
return ret
|
||||
}
|
||||
|
||||
func (widget *InlineKeyboardWidget) Serve(
|
||||
c *Context,
|
||||
updates *UpdateChan,
|
||||
) {
|
||||
for u := range updates.Chan() {
|
||||
func (widget *InlineKeyboardWidget) Serve(c *Context) {
|
||||
for u := range c.Input() {
|
||||
var act Action
|
||||
if u.CallbackQuery == nil {
|
||||
continue
|
||||
|
@ -263,11 +252,8 @@ func (widget *ReplyKeyboardWidget) Filter(
|
|||
return false
|
||||
}
|
||||
|
||||
func (widget *ReplyKeyboardWidget) Serve(
|
||||
c *Context,
|
||||
updates *UpdateChan,
|
||||
) {
|
||||
for u := range updates.Chan() {
|
||||
func (widget *ReplyKeyboardWidget) Serve(c *Context) {
|
||||
for u := range c.Input() {
|
||||
var btn *Button
|
||||
text := u.Message.Text
|
||||
btns := widget.ButtonMap()
|
||||
|
|
Loading…
Reference in a new issue