diff --git a/tg/context.go b/tg/context.go index 4c5b505..2a22bfa 100644 --- a/tg/context.go +++ b/tg/context.go @@ -1 +1,237 @@ package tg + +import ( + "fmt" + + //tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" + //"path" +) + +// The way to determine where the context is +// related to. +type ContextScope uint8 +const ( + NoContextScope ContextScope = iota + PrivateContextScope + GroupContextScope + ChannelContextScope +) + +type ContextType uint8 +const ( + NoContextType ContextType = iota + WidgetContextType + ActionContextType +) + +// 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. + Bot *Bot + skippedUpdates *UpdateChan + // Current screen ID. + path, prevPath Path +} + +// Goroutie function to handle each user. +func (c *Context) serve() { + beh := c.Bot.behaviour + if beh.Init != nil { + c.Run(beh.Init, c.Update) + } + beh.Root.Serve(c) +} + + +func (c *context) run(a Action, u *Update) { + a.Act(&Context{context: c, Update: u}) +} + +func (c *Context) Path() Path { + return c.path +} + +func (c *Context) PrevPath() Path { + return c.prevPath +} + +func (c *Context) Run(a Action, u *Update) { + if a != nil { + a.Act(c.Copy().WithUpdate(u)) + } +} + +// Only for the root widget usage. +// Skip the update sending it down to +// the underlying widget. +func (c *Context) Skip(u *Update) { + c.skippedUpdates.Send(u) +} + +// Renders the Renedrable object to the side of client +// and returns the messages it sent. +func (c *Context) Render(v Renderable) (MessageMap, error) { + return c.Bot.Render(c.Session.Id, v) +} + +// Sends to the Sendable object. +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) { + 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 + // The update that called the Context usage. + *Update + // Used as way to provide outer values redirection + // into widgets and actions. It is like arguments + // for REST API request etc. + 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) +} + +type ActionFunc func(*Context) + +func (af ActionFunc) Act(c *Context) { + af(c) +} + + +type C = Context + +// Changes screen of user to the Id one. +func (c *Context) Go(pth Path, args ...any) error { + // Getting the screen and changing to + // then executing its widget. + if !pth.IsAbs() { + pth = (c.Path() + "/" + pth).Clean() + } + + if !c.PathExist(pth) { + return ScreenNotExistErr + } + c.prevPath = c.path + c.path = pth + + // Stopping the current widget. + screen := c.Bot.behaviour.Screens[pth] + c.skippedUpdates.Close() + if screen.Widget != nil { + c.skippedUpdates = c.RunWidget(screen.Widget, args...) + } else { + panic("no widget defined for the screen") + } + + return nil +} + +func (c *Context) PathExist(pth Path) bool { + return c.Bot.behaviour.PathExist(pth) +} + +// 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), + ) + // To let widgets finish themselves before + // the channel is closed. + updates.Close() + }() + + return updates +} + +// Simple way to read strings for widgets. +func (c *Context) ReadString(pref string, args ...any) string { + var text string + c.Sendf(pref, args...) + for u := range c.Input() { + if u.Message == nil { + continue + } + text = u.Message.Text + break + } + return text +} + +// Change screen to the previous. +// To get to the parent screen use GoUp. +func (c *Context) GoPrev() { + pth := c.PrevPath() + if pth == "" { + c.Go("/") + } + c.Go(pth) +} diff --git a/tg/private.go b/tg/private.go index 2893c11..a1dbcb3 100644 --- a/tg/private.go +++ b/tg/private.go @@ -1,229 +1,2 @@ package tg -import ( - "fmt" - - //tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" - //"path" -) - -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. - Bot *Bot - skippedUpdates *UpdateChan - // Current screen ID. - path, prevPath Path -} - -// Goroutie function to handle each user. -func (c *Context) serve() { - beh := c.Bot.behaviour - if beh.Init != nil { - c.Run(beh.Init, c.Update) - } - beh.Root.Serve(c) -} - - -func (c *context) run(a Action, u *Update) { - a.Act(&Context{context: c, Update: u}) -} - -func (c *Context) Path() Path { - return c.path -} - -func (c *Context) PrevPath() Path { - return c.prevPath -} - -func (c *Context) Run(a Action, u *Update) { - if a != nil { - a.Act(c.Copy().WithUpdate(u)) - } -} - -// Only for the root widget usage. -// Skip the update sending it down to -// the underlying widget. -func (c *Context) Skip(u *Update) { - c.skippedUpdates.Send(u) -} - -// Renders the Renedrable object to the side of client -// and returns the messages it sent. -func (c *Context) Render(v Renderable) (MessageMap, error) { - return c.Bot.Render(c.Session.Id, v) -} - -// Sends to the Sendable object. -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) { - 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 - // The update that called the Context usage. - *Update - // Used as way to provide outer values redirection - // into widgets and actions. It is like arguments - // for REST API request etc. - 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) -} - -type ActionFunc func(*Context) - -func (af ActionFunc) Act(c *Context) { - af(c) -} - - -type C = Context - -// Changes screen of user to the Id one. -func (c *Context) Go(pth Path, args ...any) error { - // Getting the screen and changing to - // then executing its widget. - if !pth.IsAbs() { - pth = (c.Path() + "/" + pth).Clean() - } - - if !c.PathExist(pth) { - return ScreenNotExistErr - } - c.prevPath = c.path - c.path = pth - - // Stopping the current widget. - screen := c.Bot.behaviour.Screens[pth] - c.skippedUpdates.Close() - if screen.Widget != nil { - c.skippedUpdates = c.RunWidget(screen.Widget, args...) - } else { - panic("no widget defined for the screen") - } - - return nil -} - -func (c *Context) PathExist(pth Path) bool { - return c.Bot.behaviour.PathExist(pth) -} - -// 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), - ) - // To let widgets finish themselves before - // the channel is closed. - updates.Close() - }() - - return updates -} - -// Simple way to read strings for widgets. -func (c *Context) ReadString(pref string, args ...any) string { - var text string - c.Sendf(pref, args...) - for u := range c.Input() { - if u.Message == nil { - continue - } - text = u.Message.Text - break - } - return text -} - -// Change screen to the previous. -// To get to the parent screen use GoUp. -func (c *Context) GoPrev() { - pth := c.PrevPath() - if pth == "" { - c.Go("/") - } - c.Go(pth) -} diff --git a/tg/reply.go b/tg/reply.go index da235c6..60aca27 100644 --- a/tg/reply.go +++ b/tg/reply.go @@ -141,3 +141,7 @@ func (widget *ReplyWidget) Serve(c *Context) { } } +type DynamicReplyWidget struct { + +} +