From 32b47a49efa98ee07366c889bd25b7993a716885 Mon Sep 17 00:00:00 2001 From: Nash-Well <107937600+Nash-Well@users.noreply.github.com> Date: Tue, 27 Feb 2024 14:12:09 +0200 Subject: [PATCH] api: implement 6.6, 6.7 features (#655) * api: implement 6.6-6.7 features --- bot.go | 76 +++++++++++++++++ chat.go | 3 + inline.go | 40 +++++++++ inline_types.go | 38 ++++----- layout/example.yml | 2 +- markup.go | 15 ++-- media.go | 23 +++-- sendable.go | 3 +- stickers.go | 205 ++++++++++++++++++++++++++++++++------------- 9 files changed, 314 insertions(+), 91 deletions(-) diff --git a/bot.go b/bot.go index 0473280..8e70b1f 100644 --- a/bot.go +++ b/bot.go @@ -1161,3 +1161,79 @@ func (b *Bot) Close() (bool, error) { return resp.Result, nil } + +// BotInfo represents a single object of BotName, BotDescription, BotShortDescription instances. +type BotInfo struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + ShortDescription string `json:"short_description,omitempty"` +} + +// SetMyName change's the bot name. +func (b *Bot) SetMyName(name, language string) error { + params := map[string]string{ + "name": name, + "language_code": language, + } + + _, err := b.Raw("setMyName", params) + return err +} + +// MyName returns the current bot name for the given user language. +func (b *Bot) MyName(language string) (*BotInfo, error) { + return b.botInfo(language, "getMyName") +} + +// SetMyDescription change's the bot description, which is shown in the chat +// with the bot if the chat is empty. +func (b *Bot) SetMyDescription(desc, language string) error { + params := map[string]string{ + "description": desc, + "language_code": language, + } + + _, err := b.Raw("setMyDescription", params) + return err +} + +// MyDescription the current bot description for the given user language. +func (b *Bot) MyDescription(language string) (*BotInfo, error) { + return b.botInfo(language, "getMyDescription") +} + +// SetMyShortDescription change's the bot short description, which is shown on +// the bot's profile page and is sent together with the link when users share the bot. +func (b *Bot) SetMyShortDescription(desc, language string) error { + params := map[string]string{ + "short_description": desc, + "language_code": language, + } + + _, err := b.Raw("setMyShortDescription", params) + return err +} + +// MyShortDescription the current bot short description for the given user language. +func (b *Bot) MyShortDescription(language string) (*BotInfo, error) { + return b.botInfo(language, "getMyShortDescription") +} + +func (b *Bot) botInfo(language, key string) (*BotInfo, error) { + params := map[string]string{ + "language_code": language, + } + + data, err := b.Raw(key, params) + if err != nil { + return nil, err + } + + var resp struct { + Result *BotInfo + } + if err := json.Unmarshal(data, &resp); err != nil { + return nil, wrapError(err) + } + return resp.Result, nil +} diff --git a/chat.go b/chat.go index 74d6333..19b93ff 100644 --- a/chat.go +++ b/chat.go @@ -154,6 +154,9 @@ type ChatMemberUpdate struct { // (Optional) InviteLink which was used by the user to // join the chat; for joining by invite link events only. InviteLink *ChatInviteLink `json:"invite_link"` + + // (Optional) True, if the user joined the chat via a chat folder invite link. + ViaFolderLink bool `json:"via_chat_folder_invite_link"` } // Time returns the moment of the change in local time. diff --git a/inline.go b/inline.go index 9203d19..6a7f43f 100644 --- a/inline.go +++ b/inline.go @@ -61,6 +61,46 @@ type QueryResponse struct { // (Optional) Parameter for the start message sent to the bot when user // presses the switch button. SwitchPMParameter string `json:"switch_pm_parameter,omitempty"` + + // (Optional) A JSON-serialized object describing a button to be shown + // above inline query results. + Button *QueryResponseButton `json:"button,omitempty"` +} + +// QueryResponseButton represents a button to be shown above inline query results. +// You must use exactly one of the optional fields. +type QueryResponseButton struct { + // Label text on the button + Text string `json:"text"` + + // (Optional) Description of the Web App that will be launched when the + // user presses the button. The Web App will be able to switch back to the + // inline mode using the method switchInlineQuery inside the Web App. + WebApp *WebApp `json:"web_app"` + + // (Optional) Deep-linking parameter for the /start message sent to the bot + // when a user presses the button. 1-64 characters, only A-Z, a-z, 0-9, _ and - are allowed. + Start string `json:"start_parameter"` +} + +// SwitchInlineQuery represents an inline button that switches the current +// user to inline mode in a chosen chat, with an optional default inline query. +type SwitchInlineQuery struct { + // (Optional) The default inline query to be inserted in the input field. + // If left empty, only the bot's username will be inserted. + Query string `json:"query"` + + // (Optional) True, if private chats with users can be chosen. + AllowUserChats bool `json:"allow_user_chats"` + + // (Optional) True, if private chats with bots can be chosen. + AllowBotChats bool `json:"allow_bot_chats"` + + // (Optional) True, if group and supergroup chats can be chosen. + AllowGroupChats bool `json:"allow_group_chats"` + + // (Optional) True, if channel chats can be chosen. + AllowChannelChats bool `json:"allow_channel_chats"` } // InlineResult represents a result of an inline query that was chosen diff --git a/inline_types.go b/inline_types.go index e791afe..9b05751 100644 --- a/inline_types.go +++ b/inline_types.go @@ -91,13 +91,13 @@ type ArticleResult struct { Description string `json:"description,omitempty"` // Optional. URL of the thumbnail for the result. - ThumbURL string `json:"thumb_url,omitempty"` + ThumbURL string `json:"thumbnail_url,omitempty"` // Optional. Width of the thumbnail for the result. - ThumbWidth int `json:"thumb_width,omitempty"` + ThumbWidth int `json:"thumbnail_width,omitempty"` // Optional. Height of the thumbnail for the result. - ThumbHeight int `json:"thumb_height,omitempty"` + ThumbHeight int `json:"thumbnail_height,omitempty"` } // AudioResult represents a link to an mp3 audio file. @@ -140,13 +140,13 @@ type ContactResult struct { LastName string `json:"last_name,omitempty"` // Optional. URL of the thumbnail for the result. - ThumbURL string `json:"thumb_url,omitempty"` + ThumbURL string `json:"thumbnail_url,omitempty"` // Optional. Width of the thumbnail for the result. - ThumbWidth int `json:"thumb_width,omitempty"` + ThumbWidth int `json:"thumbnail_width,omitempty"` // Optional. Height of the thumbnail for the result. - ThumbHeight int `json:"thumb_height,omitempty"` + ThumbHeight int `json:"thumbnail_height,omitempty"` } // DocumentResult represents a link to a file. @@ -170,13 +170,13 @@ type DocumentResult struct { Description string `json:"description,omitempty"` // Optional. URL of the thumbnail (jpeg only) for the file. - ThumbURL string `json:"thumb_url,omitempty"` + ThumbURL string `json:"thumbnail_url,omitempty"` // Optional. Width of the thumbnail for the result. - ThumbWidth int `json:"thumb_width,omitempty"` + ThumbWidth int `json:"thumbnail_width,omitempty"` // Optional. Height of the thumbnail for the result. - ThumbHeight int `json:"thumb_height,omitempty"` + ThumbHeight int `json:"thumbnail_height,omitempty"` // If Cache != "", it'll be used instead Cache string `json:"document_file_id,omitempty"` @@ -199,11 +199,11 @@ type GifResult struct { Duration int `json:"gif_duration,omitempty"` // URL of the static thumbnail for the result (jpeg or gif). - ThumbURL string `json:"thumb_url"` + ThumbURL string `json:"thumbnail_url"` // Optional. MIME type of the thumbnail, must be one of // “image/jpeg”, “image/gif”, or “video/mp4”. - ThumbMIME string `json:"thumb_mime_type,omitempty"` + ThumbMIME string `json:"thumbnail_mime_type,omitempty"` // Optional. Title for the result. Title string `json:"title,omitempty"` @@ -225,7 +225,7 @@ type LocationResult struct { Title string `json:"title"` // Optional. Url of the thumbnail for the result. - ThumbURL string `json:"thumb_url,omitempty"` + ThumbURL string `json:"thumbnail_url,omitempty"` } // Mpeg4GifResult represents a link to a video animation @@ -246,11 +246,11 @@ type Mpeg4GifResult struct { Duration int `json:"mpeg4_duration,omitempty"` // URL of the static thumbnail (jpeg or gif) for the result. - ThumbURL string `json:"thumb_url,omitempty"` + ThumbURL string `json:"thumbnail_url,omitempty"` // Optional. MIME type of the thumbnail, must be one of // “image/jpeg”, “image/gif”, or “video/mp4”. - ThumbMIME string `json:"thumb_mime_type,omitempty"` + ThumbMIME string `json:"thumbnail_mime_type,omitempty"` // Optional. Title for the result. Title string `json:"title,omitempty"` @@ -286,7 +286,7 @@ type PhotoResult struct { Caption string `json:"caption,omitempty"` // URL of the thumbnail for the photo. - ThumbURL string `json:"thumb_url"` + ThumbURL string `json:"thumbnail_url"` // If Cache != "", it'll be used instead Cache string `json:"photo_file_id,omitempty"` @@ -308,13 +308,13 @@ type VenueResult struct { FoursquareID string `json:"foursquare_id,omitempty"` // Optional. URL of the thumbnail for the result. - ThumbURL string `json:"thumb_url,omitempty"` + ThumbURL string `json:"thumbnail_url,omitempty"` // Optional. Width of the thumbnail for the result. - ThumbWidth int `json:"thumb_width,omitempty"` + ThumbWidth int `json:"thumbnail_width,omitempty"` // Optional. Height of the thumbnail for the result. - ThumbHeight int `json:"thumb_height,omitempty"` + ThumbHeight int `json:"thumbnail_height,omitempty"` } // VideoResult represents a link to a page containing an embedded @@ -329,7 +329,7 @@ type VideoResult struct { MIME string `json:"mime_type"` // URL of the thumbnail (jpeg only) for the video. - ThumbURL string `json:"thumb_url"` + ThumbURL string `json:"thumbnail_url"` // Title for the result. Title string `json:"title"` diff --git a/layout/example.yml b/layout/example.yml index a7f9c79..0c6ce00 100644 --- a/layout/example.yml +++ b/layout/example.yml @@ -72,5 +72,5 @@ results: id: '{{ .ID }}' title: '{{ .Title }}' description: '{{ .Description }}' - thumb_url: '{{ .PreviewURL }}' + thumbnail_url: '{{ .PreviewURL }}' message_text: '{{ text `article_message` }}' diff --git a/markup.go b/markup.go index 29236db..82d43e0 100644 --- a/markup.go +++ b/markup.go @@ -271,13 +271,14 @@ type InlineButton struct { // It will be used as a callback endpoint. Unique string `json:"unique,omitempty"` - Text string `json:"text"` - URL string `json:"url,omitempty"` - Data string `json:"callback_data,omitempty"` - InlineQuery string `json:"switch_inline_query,omitempty"` - InlineQueryChat string `json:"switch_inline_query_current_chat"` - Login *Login `json:"login_url,omitempty"` - WebApp *WebApp `json:"web_app,omitempty"` + Text string `json:"text"` + URL string `json:"url,omitempty"` + Data string `json:"callback_data,omitempty"` + InlineQuery string `json:"switch_inline_query,omitempty"` + InlineQueryChat string `json:"switch_inline_query_current_chat"` + InlineQueryChosenChat *SwitchInlineQuery `json:"switch_inline_query_chosen_chat,omitempty"` + Login *Login `json:"login_url,omitempty"` + WebApp *WebApp `json:"web_app,omitempty"` } // MarshalJSON implements json.Marshaler interface. diff --git a/media.go b/media.go index d161aa5..cdfc3b4 100644 --- a/media.go +++ b/media.go @@ -19,7 +19,7 @@ type InputMedia struct { Type string `json:"type"` Media string `json:"media"` Caption string `json:"caption"` - Thumbnail string `json:"thumb,omitempty"` + Thumbnail string `json:"thumbnail,omitempty"` ParseMode string `json:"parse_mode,omitempty"` Entities Entities `json:"caption_entities,omitempty"` Width int `json:"width,omitempty"` @@ -113,7 +113,7 @@ type Audio struct { // (Optional) Caption string `json:"caption,omitempty"` - Thumbnail *Photo `json:"thumb,omitempty"` + Thumbnail *Photo `json:"thumbnail,omitempty"` Title string `json:"title,omitempty"` Performer string `json:"performer,omitempty"` MIME string `json:"mime_type,omitempty"` @@ -145,7 +145,7 @@ type Document struct { File // (Optional) - Thumbnail *Photo `json:"thumb,omitempty"` + Thumbnail *Photo `json:"thumbnail,omitempty"` Caption string `json:"caption,omitempty"` MIME string `json:"mime_type"` FileName string `json:"file_name,omitempty"` @@ -179,7 +179,7 @@ type Video struct { // (Optional) Caption string `json:"caption,omitempty"` - Thumbnail *Photo `json:"thumb,omitempty"` + Thumbnail *Photo `json:"thumbnail,omitempty"` Streaming bool `json:"supports_streaming,omitempty"` MIME string `json:"mime_type,omitempty"` FileName string `json:"file_name,omitempty"` @@ -215,7 +215,7 @@ type Animation struct { // (Optional) Caption string `json:"caption,omitempty"` - Thumbnail *Photo `json:"thumb,omitempty"` + Thumbnail *Photo `json:"thumbnail,omitempty"` MIME string `json:"mime_type,omitempty"` FileName string `json:"file_name,omitempty"` } @@ -265,7 +265,7 @@ type VideoNote struct { Duration int `json:"duration"` // (Optional) - Thumbnail *Photo `json:"thumb,omitempty"` + Thumbnail *Photo `json:"thumbnail,omitempty"` Length int `json:"length,omitempty"` } @@ -284,13 +284,16 @@ type Sticker struct { Height int `json:"height"` Animated bool `json:"is_animated"` Video bool `json:"is_video"` - Thumbnail *Photo `json:"thumb"` + 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"` 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 { @@ -301,6 +304,12 @@ 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/sendable.go b/sendable.go index ecaae7f..4d7d9a5 100644 --- a/sendable.go +++ b/sendable.go @@ -115,6 +115,7 @@ func (d *Document) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error func (s *Sticker) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { params := map[string]string{ "chat_id": to.Recipient(), + "emoji": s.Emoji, } b.embedSendOptions(params, opt) @@ -401,7 +402,7 @@ func (g *Game) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { func thumbnailToFilemap(thumb *Photo) map[string]File { if thumb != nil { - return map[string]File{"thumb": thumb.File} + return map[string]File{"thumbnail": thumb.File} } return nil } diff --git a/stickers.go b/stickers.go index 3e0a626..1824207 100644 --- a/stickers.go +++ b/stickers.go @@ -2,6 +2,7 @@ package telebot import ( "encoding/json" + "fmt" "strconv" ) @@ -13,21 +14,29 @@ const ( StickerCustomEmoji = "custom_emoji" ) +type StickerSetFormat = string + +const ( + StickerStatic = "static" + StickerAnimated = "animated" + StickerVideo = "video" +) + // StickerSet represents a sticker set. type StickerSet struct { - Type StickerSetType `json:"sticker_type"` - Name string `json:"name"` - Title string `json:"title"` - Animated bool `json:"is_animated"` - Video bool `json:"is_video"` - Stickers []Sticker `json:"stickers"` - Thumbnail *Photo `json:"thumb"` - PNG *File `json:"png_sticker"` - TGS *File `json:"tgs_sticker"` - WebM *File `json:"webm_sticker"` - Emojis string `json:"emojis"` - ContainsMasks bool `json:"contains_masks"` // FIXME: can be removed - MaskPosition *MaskPosition `json:"mask_position"` + Type StickerSetType `json:"sticker_type"` + Format StickerSetFormat `json:"sticker_format"` + Name string `json:"name"` + Title string `json:"title"` + 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"` } // MaskPosition describes the position on faces where @@ -50,12 +59,14 @@ const ( ) // UploadSticker uploads a PNG file with a sticker for later use. -func (b *Bot) UploadSticker(to Recipient, png *File) (*File, error) { +func (b *Bot) UploadSticker(to Recipient, s StickerSet) (*File, error) { files := map[string]File{ - "png_sticker": *png, + "sticker": s.Sticker.File, } + params := map[string]string{ - "user_id": to.Recipient(), + "user_id": to.Recipient(), + "sticker_format": s.Format, } data, err := b.sendFiles("uploadStickerFile", files, params) @@ -91,55 +102,55 @@ func (b *Bot) StickerSet(name string) (*StickerSet, error) { // CreateStickerSet creates a new sticker set. func (b *Bot) CreateStickerSet(to Recipient, s StickerSet) error { files := make(map[string]File) - if s.PNG != nil { - files["png_sticker"] = *s.PNG - } - if s.TGS != nil { - files["tgs_sticker"] = *s.TGS - } - if s.WebM != nil { - files["webm_sticker"] = *s.WebM + for i, sticker := range s.Stickers { + key := fmt.Sprint("sticker", i) + files[key] = sticker.File } - params := map[string]string{ - "user_id": to.Recipient(), - "sticker_type": s.Type, - "name": s.Name, - "title": s.Title, - "emojis": s.Emojis, - "contains_masks": strconv.FormatBool(s.ContainsMasks), + data, err := json.Marshal(s.Stickers) + if err != nil { + return err } - if s.MaskPosition != nil { - data, _ := json.Marshal(&s.MaskPosition) - params["mask_position"] = string(data) + 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), } - _, err := b.sendFiles("createNewStickerSet", files, params) + _, err = b.sendFiles("createNewStickerSet", files, params) return err } -// AddSticker adds a new sticker to the existing sticker set. -func (b *Bot) AddSticker(to Recipient, s StickerSet) error { - files := make(map[string]File) - if s.PNG != nil { - files["png_sticker"] = *s.PNG - } else if s.TGS != nil { - files["tgs_sticker"] = *s.TGS - } else if s.WebM != nil { - files["webm_sticker"] = *s.WebM - } +// 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, - "emojis": s.Emojis, } + if sticker.Emojis != nil { + data, _ := json.Marshal(s.Emojis) + params["emoji_list"] = string(data) + } if s.MaskPosition != nil { - data, _ := json.Marshal(&s.MaskPosition) + data, _ := json.Marshal(s.MaskPosition) params["mask_position"] = string(data) } + if sticker.Keywords != nil { + data, _ := json.Marshal(sticker.Keywords) + params["keywords"] = string(data) + } _, err := b.sendFiles("addStickerToSet", files, params) return err @@ -171,21 +182,92 @@ 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 { - files := make(map[string]File) - if s.PNG != nil { - files["thumb"] = *s.PNG - } else if s.TGS != nil { - files["thumb"] = *s.TGS + var ( + sticker = s.Sticker + files = make(map[string]File) + ) + files["thumbnail"] = sticker.File + + data, err := json.Marshal(sticker.File) + if err != nil { + return err } params := map[string]string{ - "name": s.Name, - "user_id": to.Recipient(), + "name": s.Name, + "user_id": to.Recipient(), + "thumbnail": string(data), } - _, err := b.sendFiles("setStickerSetThumb", files, params) + _, err = b.sendFiles("setStickerSetThumbnail", files, params) + return err +} + +// SetStickerSetTitle sets the title of a created sticker set. +func (b *Bot) SetStickerSetTitle(s StickerSet) error { + params := map[string]string{ + "name": s.Name, + "title": s.Title, + } + + _, err := b.Raw("setStickerSetTitle", params) + return err +} + +// DeleteStickerSet deletes a sticker set that was created by the bot. +func (b *Bot) DeleteStickerSet(name string) error { + params := map[string]string{"name": name} + + _, err := b.Raw("deleteStickerSet", params) + 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 { + data, err := json.Marshal(emojis) + if err != nil { + return err + } + + params := map[string]string{ + "sticker": sticker, + "emoji_list": string(data), + } + + _, err = b.Raw("setStickerEmojiList", params) + return err +} + +// SetStickerKeywords changes search keywords assigned to a regular or custom emoji sticker. +func (b *Bot) SetStickerKeywords(sticker string, keywords []string) error { + mk, err := json.Marshal(keywords) + if err != nil { + return err + } + + params := map[string]string{ + "sticker": sticker, + "keywords": string(mk), + } + + _, err = b.Raw("setStickerKeywords", params) + return err +} + +// SetStickerMaskPosition changes the mask position of a mask sticker. +func (b *Bot) SetStickerMaskPosition(sticker string, mask MaskPosition) error { + data, err := json.Marshal(mask) + if err != nil { + return err + } + + params := map[string]string{ + "sticker": sticker, + "mask_position": string(data), + } + + _, err = b.Raw("setStickerMaskPosition", params) return err } @@ -210,3 +292,14 @@ func (b *Bot) CustomEmojiStickers(ids []string) ([]Sticker, error) { } return resp.Result, nil } + +// SetCustomEmojiStickerSetThumb sets the thumbnail of a custom emoji sticker set. +func (b *Bot) SetCustomEmojiStickerSetThumb(name, id string) error { + params := map[string]string{ + "name": name, + "custom_emoji_id": id, + } + + _, err := b.Raw("setCustomEmojiStickerSetThumbnail", params) + return err +}