diff --git a/cmd/test/main.go b/cmd/test/main.go index c3c68ae..9410a79 100644 --- a/cmd/test/main.go +++ b/cmd/test/main.go @@ -152,6 +152,12 @@ var beh = tg.NewBehaviour(). } c.Sendf("You typed %q", msg) }), + tg.NewCommand("image"). + Desc("sends a sample image"). + ActionFunc(func(c *tg.Context) { + img := tg.NewFile("media/cat.jpg").Image().Caption("A cat!") + c.Send(img) + }), ) func mutateMessage(fn func(string) string) tg.ActionFunc { diff --git a/media/cat.jpg b/media/cat.jpg new file mode 100644 index 0000000..96a0f89 Binary files /dev/null and b/media/cat.jpg differ diff --git a/tg/context.go b/tg/context.go index 0ab6e46..ee408c8 100644 --- a/tg/context.go +++ b/tg/context.go @@ -6,150 +6,6 @@ import ( apix "github.com/go-telegram-bot-api/telegram-bot-api/v5" ) -type context struct { - *Session - *Bot - updates chan *Update - // Is true if currently reading the Update. - readingUpdate bool - - curScreen, prevScreen *Screen -} - -// 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.behaviour - - if beh.Init != nil { - c.run(beh.Init, nil) - } - for u := range updates { - var act Action - screen := c.curScreen - // The part is added to implement custom update handling. - if u.Message != nil { - if u.Message.IsCommand() && !c.readingUpdate { - cmdName := CommandName(u.Message.Command()) - cmd, ok := beh.Commands[cmdName] - if ok { - act = cmd.Action - } else { - } - } else { - kbd := screen.Keyboard - if kbd == nil { - if c.readingUpdate { - c.updates <- u - } - continue - } - btns := kbd.buttonMap() - text := u.Message.Text - btn, ok := btns[text] - if !ok { - if u.Message.Location != nil { - for _, b := range btns { - if b.SendLocation { - btn = b - ok = true - } - } - } else if c.readingUpdate { - // Skipping the update sending it to - // the reading goroutine. - c.updates <- u - continue - } - } - - if ok { - act = btn.Action - } - } - } else if u.CallbackQuery != nil { - cb := apix.NewCallback( - u.CallbackQuery.ID, - u.CallbackQuery.Data, - ) - data := u.CallbackQuery.Data - - _, err := c.Request(cb) - if err != nil { - panic(err) - } - kbd := screen.InlineKeyboard - if kbd == nil { - if c.readingUpdate { - c.updates <- u - } - continue - } - - btns := kbd.buttonMap() - btn, ok := btns[data] - if !ok && c.readingUpdate { - c.updates <- u - continue - } - if !ok { - c.Sendf("%q", btns) - continue - } - act = btn.Action - } - if act != nil { - c.run(act, u) - } - } -} - -func (c *context) run(a Action, u *Update) { - go a.Act(&Context{ - context: c, - Update: u, - }) -} - -// Returns the next update ignoring current screen. -func (c *context) ReadUpdate() (*Update, error) { - c.readingUpdate = true - u := <-c.updates - c.readingUpdate = false - if u == nil { - return nil, NotAvailableErr - } - - return u, nil -} - -// Returns the next text message that the user sends. -func (c *context) ReadTextMessage() (string, error) { - u, err := c.ReadUpdate() - if err != nil { - return "", err - } - if u.Message == nil { - return "", WrongUpdateType{} - } - - return u.Message.Text, nil -} - -// Sends to the user specified text. -func (c *context) Send(v ...any) error { - msg := apix.NewMessage(c.Id.ToTelegram(), fmt.Sprint(v...)) - _, err := c.Bot.Send(msg) - return err -} - -// Sends the formatted with fmt.Sprintf message to the user. -func (c *context) Sendf(format string, v ...any) error { - return c.Send(fmt.Sprintf(format, v...)) -} - // Context for interaction inside groups. type groupContext struct { *GroupSession diff --git a/tg/file.go b/tg/file.go new file mode 100644 index 0000000..238c9a2 --- /dev/null +++ b/tg/file.go @@ -0,0 +1,71 @@ +package tg + +import ( + "bufio" + "errors" + "io" + "os" + "path/filepath" +) + +type FileType int + +const ( + NoFileType FileType = iota + ImageFileType +) + +var ( + UnknownFileTypeErr = errors.New("unknown file type") +) + +type File struct { + path string + typ FileType + caption string +} + +func NewFile(path string) *File { + return &File{ + path: path, + } +} + +func (f *File) withType(typ FileType) *File { + f.typ = typ + return f +} + +func (f *File) Type() FileType { + return f.typ +} + +func (f *File) Image() *File { + return f.withType(ImageFileType) +} + +func (f *File) Caption(caption string) *File { + f.caption = caption + return f +} + +func (f *File) NeedsUpload() bool { + return true +} + +func (f *File) UploadData() (string, io.Reader, error) { + rd, err := os.Open(f.path) + if err != nil { + return "", nil, err + } + + bufRd := bufio.NewReader(rd) + + fileName := filepath.Base(f.path) + + return fileName, bufRd, nil +} + +func (f *File) SendData() string { + return "" +} diff --git a/tg/private.go b/tg/private.go index 5a889c6..4cba9f1 100644 --- a/tg/private.go +++ b/tg/private.go @@ -1,5 +1,182 @@ package tg +import ( + "fmt" + + tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" +) + +type context struct { + *Session + *Bot + updates chan *Update + // Is true if currently reading the Update. + readingUpdate bool + + curScreen, prevScreen *Screen +} + +// 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.behaviour + + if beh.Init != nil { + c.run(beh.Init, nil) + } + for u := range updates { + var act Action + screen := c.curScreen + // The part is added to implement custom update handling. + if u.Message != nil { + if u.Message.IsCommand() && !c.readingUpdate { + cmdName := CommandName(u.Message.Command()) + cmd, ok := beh.Commands[cmdName] + if ok { + act = cmd.Action + } else { + } + } else { + kbd := screen.Keyboard + if kbd == nil { + if c.readingUpdate { + c.updates <- u + } + continue + } + btns := kbd.buttonMap() + text := u.Message.Text + btn, ok := btns[text] + if !ok { + if u.Message.Location != nil { + for _, b := range btns { + if b.SendLocation { + btn = b + ok = true + } + } + } else if c.readingUpdate { + // Skipping the update sending it to + // the reading goroutine. + c.updates <- u + continue + } + } + + if ok { + act = btn.Action + } + } + } else if u.CallbackQuery != nil { + cb := tgbotapi.NewCallback( + u.CallbackQuery.ID, + u.CallbackQuery.Data, + ) + data := u.CallbackQuery.Data + + _, err := c.Request(cb) + if err != nil { + panic(err) + } + kbd := screen.InlineKeyboard + if kbd == nil { + if c.readingUpdate { + c.updates <- u + } + continue + } + + btns := kbd.buttonMap() + btn, ok := btns[data] + if !ok && c.readingUpdate { + c.updates <- u + continue + } + if !ok { + c.Sendf("%q", btns) + continue + } + act = btn.Action + } + if act != nil { + c.run(act, u) + } + } +} + +func (c *context) run(a Action, u *Update) { + go a.Act(&Context{ + context: c, + Update: u, + }) +} + +func (c *context) SendFile(f *File) error { + switch f.typ { + } + return nil +} + +// Returns the next update ignoring current screen. +func (c *context) ReadUpdate() (*Update, error) { + c.readingUpdate = true + u := <-c.updates + c.readingUpdate = false + if u == nil { + return nil, NotAvailableErr + } + + return u, nil +} + +// Returns the next text message that the user sends. +func (c *context) ReadTextMessage() (string, error) { + u, err := c.ReadUpdate() + if err != nil { + return "", err + } + if u.Message == nil { + return "", WrongUpdateType{} + } + + return u.Message.Text, nil +} + +// Sends to the user specified text. +func (c *context) Send(values ...any) error { + cid := c.Id.ToTelegram() + for _, v := range values { + var msg tgbotapi.Chattable + + switch rv := v.(type) { + case *File: + switch rv.Type() { + case ImageFileType: + msg = tgbotapi.NewPhoto(cid, rv) + default: + return UnknownFileTypeErr + } + default: + msg = tgbotapi.NewMessage( + cid, fmt.Sprint(v), + ) + } + + _, err := c.Bot.Send(msg) + if err != nil { + return err + } + } + return nil +} + +// Sends the formatted with fmt.Sprintf message to the user. +func (c *context) Sendf(format string, v ...any) error { + return c.Send(fmt.Sprintf(format, v...)) +} + // Interface to interact with the user. type Context struct { *context