From 435ad606fa6bcce037c85b8c0aefa590358af97b Mon Sep 17 00:00:00 2001 From: Demian Date: Wed, 28 Feb 2024 16:55:15 +0100 Subject: [PATCH] sticker: finish stickers api --- api.go | 13 +++ bot.go | 19 +---- bot_test.go | 5 +- media.go | 12 +-- stickers.go => sticker.go | 161 +++++++++++++++++++------------------- sticker_test.go | 61 +++++++++++++++ 6 files changed, 164 insertions(+), 107 deletions(-) rename stickers.go => sticker.go (65%) create mode 100644 sticker_test.go diff --git a/api.go b/api.go index 2dd336b..c7242d0 100644 --- a/api.go +++ b/api.go @@ -163,6 +163,19 @@ func addFileToWriter(writer *multipart.Writer, filename, field string, file inte return err } +func (f *File) process(name string, files map[string]File) string { + switch { + case f.InCloud(): + return f.FileID + case f.FileURL != "": + return f.FileURL + case f.OnDisk() || f.FileReader != nil: + files[name] = *f + return "attach://" + name + } + return "" +} + func (b *Bot) sendText(to Recipient, text string, opt *SendOptions) (*Message, error) { params := map[string]string{ "chat_id": to.Recipient(), diff --git a/bot.go b/bot.go index 8e70b1f..6b4e666 100644 --- a/bot.go +++ b/bot.go @@ -305,21 +305,8 @@ func (b *Bot) SendAlbum(to Recipient, a Album, opts ...interface{}) ([]Message, files := make(map[string]File) for i, x := range a { - var ( - repr string - data []byte - file = x.MediaFile() - ) - - switch { - case file.InCloud(): - repr = file.FileID - case file.FileURL != "": - repr = file.FileURL - case file.OnDisk() || file.FileReader != nil: - repr = "attach://" + strconv.Itoa(i) - files[strconv.Itoa(i)] = *file - default: + repr := x.MediaFile().process(strconv.Itoa(i), files) + if repr == "" { return nil, fmt.Errorf("telebot: album entry #%d does not exist", i) } @@ -332,7 +319,7 @@ func (b *Bot) SendAlbum(to Recipient, a Album, opts ...interface{}) ([]Message, im.ParseMode = sendOpts.ParseMode } - data, _ = json.Marshal(im) + data, _ := json.Marshal(im) media[i] = string(data) } diff --git a/bot_test.go b/bot_test.go index 8de89aa..cf490c0 100644 --- a/bot_test.go +++ b/bot_test.go @@ -24,6 +24,9 @@ var ( b, _ = newTestBot() // cached bot instance to avoid getMe method flooding to = &Chat{ID: chatID} // to chat recipient for send and edit methods user = &User{ID: userID} // to user recipient for some special cases + + logo = FromURL("https://telegra.ph/file/c95b8fe46dd3df15d12e5.png") + thumb = FromURL("https://telegra.ph/file/fe28e378784b3a4e367fb.png") ) func defaultSettings() Settings { @@ -502,7 +505,7 @@ func TestBot(t *testing.T) { assert.Equal(t, ErrBadRecipient, err) photo := &Photo{ - File: FromURL("https://telegra.ph/file/65c5237b040ebf80ec278.jpg"), + File: logo, Caption: t.Name(), } var msg *Message diff --git a/media.go b/media.go index cdfc3b4..9ef1127 100644 --- a/media.go +++ b/media.go @@ -280,6 +280,7 @@ func (v *VideoNote) MediaFile() *File { // Sticker object represents a WebP image, so-called sticker. type Sticker struct { File + Type StickerSetType `json:"type"` Width int `json:"width"` Height int `json:"height"` Animated bool `json:"is_animated"` @@ -287,13 +288,10 @@ type Sticker struct { Thumbnail *Photo `json:"thumbnail"` Emoji string `json:"emoji"` SetName string `json:"set_name"` - MaskPosition *MaskPosition `json:"mask_position"` PremiumAnimation *File `json:"premium_animation"` - Type StickerSetType `json:"type"` + MaskPosition *MaskPosition `json:"mask_position"` CustomEmoji string `json:"custom_emoji_id"` Repaint bool `json:"needs_repainting"` - Emojis []string `json:"emoji_list"` - Keywords []string `json:"keywords"` } func (s *Sticker) MediaType() string { @@ -304,12 +302,6 @@ func (s *Sticker) MediaFile() *File { return &s.File } -func (s *Sticker) InputMedia() InputMedia { - return InputMedia{ - Type: s.MediaType(), - } -} - // Contact object represents a contact to Telegram user. type Contact struct { PhoneNumber string `json:"phone_number"` diff --git a/stickers.go b/sticker.go similarity index 65% rename from stickers.go rename to sticker.go index 1824207..7fd060c 100644 --- a/stickers.go +++ b/sticker.go @@ -2,24 +2,34 @@ package telebot import ( "encoding/json" + "errors" "fmt" "strconv" ) -type StickerSetType = string +type ( + StickerSetType = string + StickerSetFormat = string + MaskFeature = string +) const ( - StickerRegular = "regular" - StickerMask = "mask" - StickerCustomEmoji = "custom_emoji" + StickerRegular StickerSetType = "regular" + StickerMask StickerSetType = "mask" + StickerCustomEmoji StickerSetType = "custom_emoji" ) -type StickerSetFormat = string +const ( + StickerStatic StickerSetFormat = "static" + StickerAnimated StickerSetFormat = "animated" + StickerVideo StickerSetFormat = "video" +) const ( - StickerStatic = "static" - StickerAnimated = "animated" - StickerVideo = "video" + MaskForehead MaskFeature = "forehead" + MaskEyes MaskFeature = "eyes" + MaskMouth MaskFeature = "mouth" + MaskChin MaskFeature = "chin" ) // StickerSet represents a sticker set. @@ -31,12 +41,23 @@ type StickerSet struct { Animated bool `json:"is_animated"` Video bool `json:"is_video"` Stickers []Sticker `json:"stickers"` - Sticker Sticker `json:"sticker"` Thumbnail *Photo `json:"thumbnail"` Emojis string `json:"emojis"` ContainsMasks bool `json:"contains_masks"` // FIXME: can be removed MaskPosition *MaskPosition `json:"mask_position"` Repaint bool `json:"needs_repainting"` + + // Input is a field used in createNewStickerSet method to specify a list + // of pre-defined stickers of type InputSticker to add to the set. + Input []InputSticker +} + +type InputSticker struct { + File + Sticker string `json:"sticker"` + MaskPosition *MaskPosition `json:"mask_position"` + Emojis []string `json:"emoji_list"` + Keywords []string `json:"keywords"` } // MaskPosition describes the position on faces where @@ -48,28 +69,14 @@ type MaskPosition struct { Scale float32 `json:"scale"` } -// MaskFeature defines sticker mask position. -type MaskFeature string - -const ( - FeatureForehead MaskFeature = "forehead" - FeatureEyes MaskFeature = "eyes" - FeatureMouth MaskFeature = "mouth" - FeatureChin MaskFeature = "chin" -) - -// UploadSticker uploads a PNG file with a sticker for later use. -func (b *Bot) UploadSticker(to Recipient, s StickerSet) (*File, error) { - files := map[string]File{ - "sticker": s.Sticker.File, - } - +// UploadSticker uploads a sticker file for later use. +func (b *Bot) UploadSticker(to Recipient, format StickerSetFormat, f File) (*File, error) { params := map[string]string{ "user_id": to.Recipient(), - "sticker_format": s.Format, + "sticker_format": format, } - data, err := b.sendFiles("uploadStickerFile", files, params) + data, err := b.sendFiles("uploadStickerFile", map[string]File{"0": f}, params) if err != nil { return nil, err } @@ -100,56 +107,51 @@ func (b *Bot) StickerSet(name string) (*StickerSet, error) { } // CreateStickerSet creates a new sticker set. -func (b *Bot) CreateStickerSet(to Recipient, s StickerSet) error { +func (b *Bot) CreateStickerSet(of Recipient, set *StickerSet) error { files := make(map[string]File) - for i, sticker := range s.Stickers { - key := fmt.Sprint("sticker", i) - files[key] = sticker.File + for i, s := range set.Input { + repr := s.File.process(strconv.Itoa(i), files) + if repr == "" { + return fmt.Errorf("telebot: sticker #%d does not exist", i+1) + } + set.Input[i].Sticker = repr } - data, err := json.Marshal(s.Stickers) - if err != nil { - return err - } + data, _ := json.Marshal(set.Input) params := map[string]string{ - "user_id": to.Recipient(), - "name": s.Name, - "title": s.Title, - "sticker_type": s.Type, - "sticker_format": s.Format, - "stickers": string(data), - "needs_repainting": strconv.FormatBool(s.Repaint), + "user_id": of.Recipient(), + "name": set.Name, + "title": set.Title, + "sticker_format": set.Format, + "stickers": string(data), + } + if set.Type != "" { + params["sticker_type"] = set.Type + } + if set.Repaint { + params["needs_repainting"] = "true" } - _, err = b.sendFiles("createNewStickerSet", files, params) + _, err := b.sendFiles("createNewStickerSet", files, params) return err } // AddStickerToSet adds a new sticker to the existing sticker set. -func (b *Bot) AddStickerToSet(to Recipient, s StickerSet) error { - var ( - files = make(map[string]File) - sticker = s.Sticker - ) - files["sticker"] = sticker.File - - params := map[string]string{ - "user_id": to.Recipient(), - "name": s.Name, +func (b *Bot) AddStickerToSet(of Recipient, name string, sticker InputSticker) error { + files := make(map[string]File) + repr := sticker.File.process("0", files) + if repr == "" { + return errors.New("telebot: sticker does not exist") } - if sticker.Emojis != nil { - data, _ := json.Marshal(s.Emojis) - params["emoji_list"] = string(data) - } - if s.MaskPosition != nil { - data, _ := json.Marshal(s.MaskPosition) - params["mask_position"] = string(data) - } - if sticker.Keywords != nil { - data, _ := json.Marshal(sticker.Keywords) - params["keywords"] = string(data) + sticker.Sticker = repr + data, _ := json.Marshal(sticker) + + params := map[string]string{ + "user_id": of.Recipient(), + "name": name, + "sticker": string(data), } _, err := b.sendFiles("addStickerToSet", files, params) @@ -182,25 +184,24 @@ func (b *Bot) DeleteSticker(sticker string) error { // up to 32 kilobytes in size. // // Animated sticker set thumbnail can't be uploaded via HTTP URL. -func (b *Bot) SetStickerSetThumb(to Recipient, s StickerSet) error { - var ( - sticker = s.Sticker - files = make(map[string]File) - ) - files["thumbnail"] = sticker.File - - data, err := json.Marshal(sticker.File) - if err != nil { - return err +func (b *Bot) SetStickerSetThumb(of Recipient, set *StickerSet) error { + if set.Thumbnail == nil { + return errors.New("telebot: thumbnail is required") + } + + files := make(map[string]File) + repr := set.Thumbnail.File.process("thumb", files) + if repr == "" { + return errors.New("telebot: thumbnail does not exist") } params := map[string]string{ - "name": s.Name, - "user_id": to.Recipient(), - "thumbnail": string(data), + "user_id": of.Recipient(), + "name": set.Name, + "thumbnail": repr, } - _, err = b.sendFiles("setStickerSetThumbnail", files, params) + _, err := b.sendFiles("setStickerSetThumbnail", files, params) return err } @@ -223,8 +224,8 @@ func (b *Bot) DeleteStickerSet(name string) error { return err } -// SetStickerEmojiList changes the list of emoji assigned to a regular or custom emoji sticker. -func (b *Bot) SetStickerEmojiList(sticker string, emojis []string) error { +// SetStickerEmojis changes the list of emoji assigned to a regular or custom emoji sticker. +func (b *Bot) SetStickerEmojis(sticker string, emojis []string) error { data, err := json.Marshal(emojis) if err != nil { return err diff --git a/sticker_test.go b/sticker_test.go new file mode 100644 index 0000000..8571b64 --- /dev/null +++ b/sticker_test.go @@ -0,0 +1,61 @@ +package telebot + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestStickerSet(t *testing.T) { + if b == nil { + t.Skip("Cached bot instance is bad (probably wrong or empty TELEBOT_SECRET)") + } + if userID == 0 { + t.Skip("USER_ID is required for StickerSet methods test") + } + + input := []InputSticker{ + { + File: FromURL("https://placehold.co/512/000000/FFFFFF/png"), + Emojis: []string{"🤖"}, + Keywords: []string{"telebot", "robot", "bot"}, + }, + { + File: FromURL("https://placehold.co/512/000000/999999/png"), + Emojis: []string{"🤖"}, + Keywords: []string{"telebot", "robot", "bot"}, + }, + } + + original := &StickerSet{ + Name: fmt.Sprintf("telebot_%d_by_%s", time.Now().Unix(), b.Me.Username), + Type: StickerRegular, + Format: StickerStatic, + Title: "Telebot Stickers", + Input: input[:1], + } + + // 1 + err := b.CreateStickerSet(user, original) + require.NoError(t, err) + // 2 + err = b.AddStickerToSet(user, original.Name, input[1]) + require.NoError(t, err) + + original.Thumbnail = &Photo{File: thumb} + err = b.SetStickerSetThumb(user, original) + require.NoError(t, err) + + set, err := b.StickerSet(original.Name) + require.NoError(t, err) + require.Equal(t, original.Name, set.Name) + require.Equal(t, len(input), len(set.Stickers)) + + _, err = b.Send(user, &set.Stickers[0]) + require.NoError(t, err) + + _, err = b.Send(user, &set.Stickers[1]) + require.NoError(t, err) +}