tg/context.go

240 lines
4.8 KiB
Go

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
}