Skip to content

Commit

Permalink
bot: implement SendPaid
Browse files Browse the repository at this point in the history
  • Loading branch information
demget committed Aug 10, 2024
1 parent 9c7a18b commit 7655e7a
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 62 deletions.
27 changes: 0 additions & 27 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,33 +212,6 @@ func (b *Bot) sendMedia(media Media, params map[string]string, files map[string]
return extractMessage(data)
}

func (b *Bot) sendPaidMedia(to Recipient, media Media, stars int, opts ...interface{}) (*Message, error) {
sendOpts := b.extractOptions(opts)

kind := media.MediaType()
what := "send" + strings.Title(kind)

if kind == "videoNote" {
kind = "video_note"
}

sendFiles := map[string]File{kind: *media.MediaFile()}
sendFiles[media.MediaFile().FileURL] = *media.MediaFile()

params := map[string]string{
"chat_id": to.Recipient(),
"star_count": strconv.Itoa(stars),
}
b.embedSendOptions(params, sendOpts)

data, err := b.sendFiles(what, sendFiles, params)
if err != nil {
return nil, err
}

return extractMessage(data)
}

func (b *Bot) getMe() (*User, error) {
data, err := b.Raw("getMe", nil)
if err != nil {
Expand Down
47 changes: 47 additions & 0 deletions bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,53 @@ func (b *Bot) Send(to Recipient, what interface{}, opts ...interface{}) (*Messag
}
}

// SendPaid sends multiple instances of paid media as a single message.
// To include the caption, make sure the first PaidInputtable of an album has it.
func (b *Bot) SendPaid(to Recipient, stars int, a PaidAlbum, opts ...interface{}) (*Message, error) {
if to == nil {
return nil, ErrBadRecipient
}

params := map[string]string{
"chat_id": to.Recipient(),
"star_count": strconv.Itoa(stars),
}
sendOpts := b.extractOptions(opts)

media := make([]string, len(a))
files := make(map[string]File)

for i, x := range a {
repr := x.MediaFile().process(strconv.Itoa(i), files)
if repr == "" {
return nil, fmt.Errorf("telebot: paid media entry #%d does not exist", i)
}

im := x.InputMedia()
im.Media = repr

if i == 0 {
params["caption"] = im.Caption
if im.CaptionAbove {
params["show_caption_above_media"] = "true"
}
}

data, _ := json.Marshal(im)
media[i] = string(data)
}

params["media"] = "[" + strings.Join(media, ",") + "]"
b.embedSendOptions(params, sendOpts)

data, err := b.sendFiles("sendPaidMedia", files, params)
if err != nil {
return nil, err
}

return extractMessage(data)
}

// SendAlbum sends multiple instances of media as a single message.
// To include the caption, make sure the first Inputtable of an album has it.
// From all existing options, it only supports tele.Silent.
Expand Down
32 changes: 26 additions & 6 deletions bot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ import (

var (
// required to test send and edit methods
token = os.Getenv("TELEBOT_SECRET")
chatID, _ = strconv.ParseInt(os.Getenv("CHAT_ID"), 10, 64)
userID, _ = strconv.ParseInt(os.Getenv("USER_ID"), 10, 64)
token = os.Getenv("TELEBOT_SECRET")
b, _ = newTestBot() // cached bot instance to avoid getMe method flooding

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
chatID, _ = strconv.ParseInt(os.Getenv("CHAT_ID"), 10, 64)
userID, _ = strconv.ParseInt(os.Getenv("USER_ID"), 10, 64)
channelID, _ = strconv.ParseInt(os.Getenv("CHANNEL_ID"), 10, 64)

to = &Chat{ID: chatID} // to chat recipient for send and edit methods
user = &User{ID: userID} // to user recipient for some special cases
channel = &Chat{ID: channelID} // to channel recipient for some special cases

logo = FromURL("https://telegra.ph/file/c95b8fe46dd3df15d12e5.png")
thumb = FromURL("https://telegra.ph/file/fe28e378784b3a4e367fb.png")
Expand Down Expand Up @@ -533,6 +536,23 @@ func TestBot(t *testing.T) {
assert.NotEmpty(t, msgs[0].AlbumID)
})

t.Run("SendPaid()", func(t *testing.T) {
_, err = b.SendPaid(nil, 0, nil)
assert.Equal(t, ErrBadRecipient, err)

_, err = b.SendPaid(channel, 0, nil)
assert.Error(t, err)

photo2 := *photo
photo2.Caption = ""

msg, err := b.SendPaid(channel, 1, PaidAlbum{photo, &photo2}, ModeHTML)
require.NoError(t, err)
require.NotNil(t, msg)
assert.Equal(t, 1, msg.PaidMedia.Stars)
assert.Equal(t, 2, len(msg.PaidMedia.PaidMedia))
})

t.Run("EditCaption()+ParseMode", func(t *testing.T) {
b.parseMode = "html"

Expand Down
49 changes: 35 additions & 14 deletions media.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,6 @@ type InputMedia struct {
HasSpoiler bool `json:"has_spoiler,omitempty"`
}

type PaidMediaInfo struct {
Stars int `json:"star_count"`
PaidMedia []PaidMedia `json:"paid_media"`
}

type PaidMedia struct {
Type string `json:"type"`
Photo *Photo `json:"photo,omitempty"`
Video Video `json:"video,omitempty"`
Width int `json:"width,omitempty"`
Height int `json:"height,omitempty"`
Duration int `json:"duration,omitempty"`
}

// Inputtable is a generic type for all kinds of media you
// can put into an album.
type Inputtable interface {
Expand Down Expand Up @@ -116,6 +102,10 @@ func (p *Photo) InputMedia() InputMedia {
}
}

func (p *Photo) Paid() bool {
return true
}

// UnmarshalJSON is custom unmarshaller required to abstract
// away the hassle of treating different thumbnail sizes.
// Instead, Telebot chooses the hi-res one and just sticks to it.
Expand Down Expand Up @@ -177,6 +167,7 @@ func (a *Audio) InputMedia() InputMedia {
Title: a.Title,
Performer: a.Performer,
}

}

// Document object represents a general file (as opposed to Photo or Audio).
Expand Down Expand Up @@ -249,6 +240,10 @@ func (v *Video) InputMedia() InputMedia {
}
}

func (v *Video) Paid() bool {
return true
}

// Animation object represents a animation file.
type Animation struct {
File
Expand Down Expand Up @@ -414,3 +409,29 @@ var (
Slot = &Dice{Type: "🎰"}
Bowl = &Dice{Type: "🎳"}
)

// PaidInputtable is a generic type for all kinds of media you
// can put into an album that are paid.
type PaidInputtable interface {
Inputtable

// Paid shows if the media is paid.
Paid() bool
}

// PaidAlbum lets you group multiple paid media into a single message.
type PaidAlbum []PaidInputtable

type PaidMedias struct {
Stars int `json:"star_count"`
PaidMedia []PaidMedia `json:"paid_media"`
}

type PaidMedia struct {
Type string `json:"type"`
Photo *Photo `json:"photo"` // photo
Video *Video `json:"video"` // video
Width int `json:"width"` // preview only
Height int `json:"height"` // preview only
Duration int `json:"duration"` // preview only
}
20 changes: 10 additions & 10 deletions message.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ type Message struct {

// (Optional) Information about the message that is being replied to,
// which may come from another chat or forum topic.
ExternalReplyInfo *ExternalReplyInfo `json:"external_reply"`
ExternalReply *ExternalReply `json:"external_reply"`

// (Optional) For replies that quote part of the original message,
// the quoted part of the message.
Expand Down Expand Up @@ -129,7 +129,7 @@ type Message struct {
Document *Document `json:"document"`

// Message contains paid media; information about the paid media
PaidMedia PaidMediaInfo `json:"paid_media"`
PaidMedia PaidMedias `json:"paid_media"`

// For a photo, all available sizes (thumbnails).
Photo *Photo `json:"photo"`
Expand Down Expand Up @@ -639,9 +639,9 @@ func (mo *MessageOrigin) Time() time.Time {
return time.Unix(mo.DateUnixtime, 0)
}

// ExternalReplyInfo contains information about a message that is being replied to,
// ExternalReply contains information about a message that is being replied to,
// which may come from another chat or forum topic.
type ExternalReplyInfo struct {
type ExternalReply struct {
// Origin of the message replied to by the given message.
Origin *MessageOrigin `json:"origin"`

Expand All @@ -666,9 +666,6 @@ type ExternalReplyInfo struct {
// (Optional) Message is a general file, information about the file.
Document *Document `json:"document"`

// Message contains paid media; information about the paid media
PaidMedia PaidMediaInfo `json:"paid_media"`

// (Optional) Message is a photo, available sizes of the photo.
Photo []Photo `json:"photo"`

Expand All @@ -687,9 +684,6 @@ type ExternalReplyInfo struct {
// (Optional) Message is a voice message, information about the file.
Voice *Voice `json:"voice"`

// (Optional) True, if the message media is covered by a spoiler animation.
HasMediaSpoiler bool `json:"has_media_spoiler"`

// (Optional) Message is a shared contact, information about the contact.
Contact *Contact `json:"contact"`

Expand All @@ -716,6 +710,12 @@ type ExternalReplyInfo struct {

// (Optional) A giveaway with public winners was completed.
GiveawayWinners *GiveawayWinners `json:"giveaway_winners"`

// Message contains paid media; information about the paid media
PaidMedia PaidMedias `json:"paid_media"`

// (Optional) True, if the message media is covered by a spoiler animation.
HasMediaSpoiler bool `json:"has_media_spoiler"`
}

// ReplyParams describes reply parameters for the message that is being sent.
Expand Down
2 changes: 1 addition & 1 deletion options.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ type SendOptions struct {
BusinessConnectionID string

// Unique identifier of the message effect to be added to the message; for private chats only
EffectID string `json:"message_effect_id"`
EffectID string
}

func (og *SendOptions) copy() *SendOptions {
Expand Down
8 changes: 4 additions & 4 deletions topic.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ func (b *Bot) CreateTopic(chat *Chat, topic *Topic) (*Topic, error) {
if topic.IconColor != 0 {
params["icon_color"] = strconv.Itoa(topic.IconColor)
}
if topic.IconCustomEmoji != "" {
params["icon_custom_emoji_id"] = topic.IconCustomEmoji
if topic.IconCustomEmojiID != "" {
params["icon_custom_emoji_id"] = topic.IconCustomEmojiID
}

data, err := b.Raw("createForumTopic", params)
Expand All @@ -50,8 +50,8 @@ func (b *Bot) EditTopic(chat *Chat, topic *Topic) error {
if topic.Name != "" {
params["name"] = topic.Name
}
if topic.IconCustomEmoji != "" {
params["icon_custom_emoji_id"] = topic.IconCustomEmoji
if topic.IconCustomEmojiID != "" {
params["icon_custom_emoji_id"] = topic.IconCustomEmojiID
}

_, err := b.Raw("editForumTopic", params)
Expand Down

0 comments on commit 7655e7a

Please sign in to comment.