package tg import ( "fmt" "io" "net/http" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" //"path" ) // Interface to interact with the user. type Context struct { session *Session // The update that called the Context usage. update Update // Used as way to provide outer values redirection // into widgets and actions. It is like arguments // for REST API request etc. arg any typ ContextType // Instead of updates as argument. input *UpdateChan } // Run commands as other user. Was implemented to // make other user to leave the bot at first but // maybe you will find another usage for this. // Returns users context by specified session ID // or nil if the user is not logged in. func (c Context) As(sid SessionId) Context { n, ok := c.Bot.contexts[sid] if !ok { return nil } return &Context{ context: n, } } // General type function to define actions, single component widgets // and components themselves. type Func func(Context) func (f Func) Act(c Context) { f(c) } func (f Func) Serve(c Context) { f(c) } func(f Func) Filter(_ *Update) bool { return false } func (f Func) Render(_ *Context) UI { return UI{ f, } } type ContextType uint8 const ( NoContextType ContextType = iota WidgetContextType ActionContextType ) // Goroutie function to handle each user. func (c Context) serve() { beh := c.Bot.behaviour c.Run(beh.Init) beh.Root.Serve(c) } func (c Context) Path() Path { ln := len(c.pathHistory) if ln == 0 { return "" } return c.pathHistory[ln-1] } func (c Context) Arg() any { return c.arg } func (c Context) Run(a Action) { if a != nil { a.Act(c) } } // 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) } // Sends to the Sendable object. func (c Context) Send(v Sendable) (Message, error) { config := v.SendConfig(c.Session.Id, c.Bot) if config.Error != nil { return nil, config.Error } msg, err := c.Bot.Api.Send(config.ToApi()) if err != nil { return nil, err } return &msg, nil } // Sends the formatted with fmt.Sprintf message to the user // using default Markdown parsing format. func (c Context) Sendf(format string, v ...any) (Message, error) { return c.Send(NewMessage(format, v...)) } // Same as Sendf but uses Markdown 2 format for parsing. func (c Context) Sendf2(format string, v ...any) (Message, error) { return c.Send(NewMessage(fmt.Sprintf(format, v...)).MD2()) } // Same as Sendf but uses HTML format for parsing. func (c Context) SendfHTML(format string, v ...any) (Message, error) { return c.Send(NewMessage(fmt.Sprintf(format, v...)).HTML()) } // Send the message in raw format escaping all the special characters. func (c Context) SendfR(format string, v ...any) (Message, error) { return c.Send(NewMessage(Escape2(fmt.Sprintf(format, v...))).MD2()) } // Get the input for current widget. // Should be used inside handlers (aka "Serve"). func (c Context) Input() chan Update { return c.input.Chan() } 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 } func (c Context) Go(pth Path) error { return c.session.go_(pth, nil) } func (c Context) GoWithArg(pth Path, arg any) error { return c.session.go_(pth, arg) } // Customized actions for the bot. type Action interface { Act(Context) } type ActionFunc func(Context) func (af ActionFunc) Act(c *Context) { af(c) } func (c Context) History() []Path { return c.session.pathHistory } func (c Context) PathExist(pth Path) bool { return c.bot.behaviour.PathExist(pth) } // Simple way to read strings for widgets with // the specified prompt. func (c Context) ReadString(promptf string, args ...any) string { var text string if pref != "" { c.Sendf(promptf, args...) } for u := range c.Input() { if u == nil { break } if u.Message == nil { continue } text = u.Message.Text break } return text } func (c Context) Update() Update { return c.update } // Returns the reader for specified file ID and path. func (c *Context) GetFile(fileId FileId) (io.ReadCloser, string, error) { file, err := c.Bot.Api.GetFile(tgbotapi.FileConfig{FileID:string(fileId)}) if err != nil { return nil, "", err } r, err := http.Get(fmt.Sprintf( "https://api.telegram.org/file/bot%s/%s", c.Bot.Api.Token, file.FilePath, )) if err != nil { return nil, "", err } if r.StatusCode != 200 { return nil, "", StatusCodeErr } return r.Body, file.FilePath, nil } func (c *Context) ReadFile(fileId FileId) ([]byte, string, error) { file, pth, err := c.GetFile(fileId) if err != nil { return nil, "", err } defer file.Close() bts, err := io.ReadAll(file) if err != nil { return nil, "", err } return bts, pth, nil }