diff --git a/bot.go b/bot.go index 1f2cbb5..681dbd4 100644 --- a/bot.go +++ b/bot.go @@ -26,8 +26,9 @@ type BotAPI struct { Debug bool `json:"debug"` Buffer int `json:"buffer"` - Self User `json:"-"` - Client *http.Client `json:"-"` + Self User `json:"-"` + Client *http.Client `json:"-"` + shutdownChannel chan interface{} } // NewBotAPI creates a new BotAPI instance. @@ -43,9 +44,10 @@ func NewBotAPI(token string) (*BotAPI, error) { // It requires a token, provided by @BotFather on Telegram. func NewBotAPIWithClient(token string, client *http.Client) (*BotAPI, error) { bot := &BotAPI{ - Token: token, - Client: client, - Buffer: 100, + Token: token, + Client: client, + Buffer: 100, + shutdownChannel: make(chan interface{}), } self, err := bot.GetMe() @@ -193,7 +195,6 @@ func (bot *BotAPI) UploadFile(endpoint string, params map[string]string, fieldna } ms.SetupRequest(req) - res, err := bot.Client.Do(req) if err != nil { return APIResponse{}, err @@ -308,7 +309,6 @@ func (bot *BotAPI) uploadAndSend(method string, config Fileable) (Message, error } file := config.getFile() - resp, err := bot.UploadFile(method, params, config.name(), file) if err != nil { return Message{}, err @@ -484,6 +484,12 @@ func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (UpdatesChannel, error) { go func() { for { + select { + case <-bot.shutdownChannel: + return + default: + } + updates, err := bot.GetUpdates(config) if err != nil { log.Println(err) @@ -505,12 +511,22 @@ func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (UpdatesChannel, error) { return ch, nil } +// StopReceivingUpdates stops the go routine which receives updates +func (bot *BotAPI) StopReceivingUpdates() { + if bot.Debug { + log.Println("Stopping the update receiver routine...") + } + close(bot.shutdownChannel) +} + // ListenForWebhook registers a http handler for a webhook. func (bot *BotAPI) ListenForWebhook(pattern string) UpdatesChannel { ch := make(chan Update, bot.Buffer) http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) { bytes, _ := ioutil.ReadAll(r.Body) + r.Body.Close() + var update Update json.Unmarshal(bytes, &update) @@ -949,154 +965,3 @@ func (bot *BotAPI) DeleteChatPhoto(config DeleteChatPhotoConfig) (APIResponse, e return bot.MakeRequest(config.method(), v) } - -// GetStickerSet is used to get a sticker set. -func (bot *BotAPI) GetStickerSet(name string) (StickerSet, error) { - v := url.Values{} - - v.Add("name", name) - resp, err := bot.MakeRequest("getStickerSet", v) - if err != nil { - return StickerSet{}, err - } - - var stickerSet StickerSet - err = json.Unmarshal(resp.Result, &stickerSet) - - return stickerSet, err -} - -// UploadStickerFile uploads a .png file with a sticker for later use in -// createNewStickerSet and addStickerToSet methods (can be used multiple times). -// -// File should be a string to a file path, a FileBytes struct, or a FileReader -// struct. -func (bot *BotAPI) UploadStickerFile(userID int, file interface{}) (APIResponse, File, error) { - switch file.(type) { - case url.URL: - return APIResponse{}, File{}, errors.New(ErrBadFileType) - } - - params := make(map[string]string) - params["user_id"] = strconv.Itoa(userID) - - resp, err := bot.UploadFile("uploadStickerFile", params, "png_sticker", file) - if err != nil { - return resp, File{}, err - } - - returnFile := File{} - err = json.Unmarshal(resp.Result, &returnFile) - if err != nil { - return resp, File{}, err - } - - return resp, returnFile, nil -} - -// CreateNewStickerSet creates a new sticker set owned by a user. The bot will -// be able to edit the created sticker set. -func (bot *BotAPI) CreateNewStickerSet(config CreateNewStickerSetConfig) (APIResponse, error) { - params := make(map[string]string) - - params["user_id"] = strconv.Itoa(config.UserID) - params["name"] = config.Name - params["title"] = config.Title - params["emojis"] = config.Emojis - - if config.ContainsMasks { - params["contains_masks"] = strconv.FormatBool(config.ContainsMasks) - } - - if config.MaskPosition != nil { - maskPosition, err := json.Marshal(&config.MaskPosition) - if err != nil { - return APIResponse{}, err - } - - params["mask_position"] = string(maskPosition) - } - - return bot.UploadFile("createNewStickerSet", params, "png_sticker", config.PNGSticker) -} - -// CreateNewStickerSet creates a new sticker set owned by a user. The bot will -// be able to edit the created sticker set. -func (bot *BotAPI) CreateNewStickerSetFileId(config CreateNewStickerSetConfig) (APIResponse, error) { - v := url.Values{} - v.Add("png_sticker", config.PNGSticker.(string)) - v.Add("user_id", strconv.Itoa(config.UserID)) - v.Add("name", config.Name) - v.Add("title", config.Title) - v.Add("emojis", config.Emojis) - - if config.MaskPosition != nil { - maskPosition, err := json.Marshal(&config.MaskPosition) - if err != nil { - return APIResponse{}, err - } - v.Add("mask_position", string(maskPosition)) - } - if config.ContainsMasks { - v.Add("contains_masks", strconv.FormatBool(config.ContainsMasks)) - } - return bot.MakeRequest("createNewStickerSet", v) -} - -// AddStickerToSet adds a new sticker to a set created by the bot. -func (bot *BotAPI) AddStickerToSet(config AddStickerToSetConfig) (APIResponse, error) { - params := make(map[string]string) - - params["user_id"] = strconv.Itoa(config.UserID) - params["name"] = config.Name - params["emojis"] = config.Emojis - - if config.MaskPosition != nil { - maskPosition, err := json.Marshal(&config.MaskPosition) - if err != nil { - return APIResponse{}, err - } - - params["mask_position"] = string(maskPosition) - } - - return bot.UploadFile("addStickerToSet", params, "png_sticker", config.PNGSticker) -} - -// AddStickerToSet adds a new sticker to a set created by the bot. -func (bot *BotAPI) AddStickerToSetFileId(config AddStickerToSetConfig) (APIResponse, error) { - - v := url.Values{} - v.Add("png_sticker", config.PNGSticker.(string)) - v.Add("user_id", strconv.Itoa(config.UserID)) - v.Add("name", config.Name) - v.Add("emojis", config.Emojis) - - if config.MaskPosition != nil { - maskPosition, err := json.Marshal(&config.MaskPosition) - if err != nil { - return APIResponse{}, err - } - v.Add("mask_position", string(maskPosition)) - } - - return bot.MakeRequest("addStickerToSet", v) -} - -// SetStickerPositionInSet moves a sticker in a set created by the bot to a specific position. -func (bot *BotAPI) SetStickerPositionInSet(config SetStickerPositionInSetConfig) (APIResponse, error) { - v, _ := config.values() - - bot.debugLog(config.method(), v, nil) - - return bot.MakeRequest(config.method(), v) -} - -// DeleteStickerFromSet deletes a sticker from a set created by the bot. -func (bot *BotAPI) DeleteStickerFromSet(sticker string) (APIResponse, error) { - v := url.Values{} - - v.Add("sticker", sticker) - - return bot.MakeRequest("deleteStickerFromSet", v) -} diff --git a/configs.go b/configs.go index 528155b..a83e70b 100644 --- a/configs.go +++ b/configs.go @@ -240,47 +240,6 @@ func (config ForwardConfig) method() string { return "forwardMessage" } -// AnimationConfig contains information about a SendAnimation request. -type AnimationConfig struct { - BaseFile - Caption string -} - -// Params returns a map[string]string representation of AnimationConfig. -func (config AnimationConfig) params() (map[string]string, error) { - params, _ := config.BaseFile.params() - - if config.Caption != "" { - params["caption"] = config.Caption - } - - return params, nil -} - -// Values returns a url.Values representation of AnimationConfig. -func (config AnimationConfig) values() (url.Values, error) { - v, err := config.BaseChat.values() - if err != nil { - return v, err - } - - v.Add(config.name(), config.FileID) - if config.Caption != "" { - v.Add("caption", config.Caption) - } - return v, nil -} - -// name returns the field name for the Animation. -func (config AnimationConfig) name() string { - return "animation" -} - -// method returns Telegram API method name for sending Animation. -func (config AnimationConfig) method() string { - return "sendAnimation" -} - // PhotoConfig contains information about a SendPhoto request. type PhotoConfig struct { BaseFile @@ -294,10 +253,9 @@ func (config PhotoConfig) params() (map[string]string, error) { if config.Caption != "" { params["caption"] = config.Caption - } - - if config.ParseMode != "" { - params["parse_mode"] = config.ParseMode + if config.ParseMode != "" { + params["parse_mode"] = config.ParseMode + } } return params, nil @@ -313,10 +271,11 @@ func (config PhotoConfig) values() (url.Values, error) { v.Add(config.name(), config.FileID) if config.Caption != "" { v.Add("caption", config.Caption) + if config.ParseMode != "" { + v.Add("parse_mode", config.ParseMode) + } } - if config.ParseMode != "" { - v.Add("parse_mode", config.ParseMode) - } + return v, nil } @@ -334,6 +293,7 @@ func (config PhotoConfig) method() string { type AudioConfig struct { BaseFile Caption string + ParseMode string Duration int Performer string Title string @@ -359,6 +319,9 @@ func (config AudioConfig) values() (url.Values, error) { } if config.Caption != "" { v.Add("caption", config.Caption) + if config.ParseMode != "" { + v.Add("parse_mode", config.ParseMode) + } } return v, nil @@ -380,6 +343,9 @@ func (config AudioConfig) params() (map[string]string, error) { } if config.Caption != "" { params["caption"] = config.Caption + if config.ParseMode != "" { + params["parse_mode"] = config.ParseMode + } } return params, nil @@ -398,7 +364,8 @@ func (config AudioConfig) method() string { // DocumentConfig contains information about a SendDocument request. type DocumentConfig struct { BaseFile - Caption string + Caption string + ParseMode string } // values returns a url.Values representation of DocumentConfig. @@ -411,6 +378,9 @@ func (config DocumentConfig) values() (url.Values, error) { v.Add(config.name(), config.FileID) if config.Caption != "" { v.Add("caption", config.Caption) + if config.ParseMode != "" { + v.Add("parse_mode", config.ParseMode) + } } return v, nil @@ -422,6 +392,9 @@ func (config DocumentConfig) params() (map[string]string, error) { if config.Caption != "" { params["caption"] = config.Caption + if config.ParseMode != "" { + params["parse_mode"] = config.ParseMode + } } return params, nil @@ -474,8 +447,9 @@ func (config StickerConfig) method() string { // VideoConfig contains information about a SendVideo request. type VideoConfig struct { BaseFile - Duration int - Caption string + Duration int + Caption string + ParseMode string } // values returns a url.Values representation of VideoConfig. @@ -491,6 +465,9 @@ func (config VideoConfig) values() (url.Values, error) { } if config.Caption != "" { v.Add("caption", config.Caption) + if config.ParseMode != "" { + v.Add("parse_mode", config.ParseMode) + } } return v, nil @@ -502,6 +479,9 @@ func (config VideoConfig) params() (map[string]string, error) { if config.Caption != "" { params["caption"] = config.Caption + if config.ParseMode != "" { + params["parse_mode"] = config.ParseMode + } } return params, nil @@ -517,6 +497,59 @@ func (config VideoConfig) method() string { return "sendVideo" } +// AnimationConfig contains information about a SendAnimation request. +type AnimationConfig struct { + BaseFile + Duration int + Caption string + ParseMode string +} + +// values returns a url.Values representation of AnimationConfig. +func (config AnimationConfig) values() (url.Values, error) { + v, err := config.BaseChat.values() + if err != nil { + return v, err + } + + v.Add(config.name(), config.FileID) + if config.Duration != 0 { + v.Add("duration", strconv.Itoa(config.Duration)) + } + if config.Caption != "" { + v.Add("caption", config.Caption) + if config.ParseMode != "" { + v.Add("parse_mode", config.ParseMode) + } + } + + return v, nil +} + +// params returns a map[string]string representation of AnimationConfig. +func (config AnimationConfig) params() (map[string]string, error) { + params, _ := config.BaseFile.params() + + if config.Caption != "" { + params["caption"] = config.Caption + if config.ParseMode != "" { + params["parse_mode"] = config.ParseMode + } + } + + return params, nil +} + +// name returns the field name for the Animation. +func (config AnimationConfig) name() string { + return "animation" +} + +// method returns Telegram API method name for sending Animation. +func (config AnimationConfig) method() string { + return "sendAnimation" +} + // VideoNoteConfig contains information about a SendVideoNote request. type VideoNoteConfig struct { BaseFile @@ -571,8 +604,9 @@ func (config VideoNoteConfig) method() string { // VoiceConfig contains information about a SendVoice request. type VoiceConfig struct { BaseFile - Caption string - Duration int + Caption string + ParseMode string + Duration int } // values returns a url.Values representation of VoiceConfig. @@ -588,6 +622,9 @@ func (config VoiceConfig) values() (url.Values, error) { } if config.Caption != "" { v.Add("caption", config.Caption) + if config.ParseMode != "" { + v.Add("parse_mode", config.ParseMode) + } } return v, nil @@ -602,6 +639,9 @@ func (config VoiceConfig) params() (map[string]string, error) { } if config.Caption != "" { params["caption"] = config.Caption + if config.ParseMode != "" { + params["parse_mode"] = config.ParseMode + } } return params, nil @@ -617,6 +657,32 @@ func (config VoiceConfig) method() string { return "sendVoice" } +// MediaGroupConfig contains information about a sendMediaGroup request. +type MediaGroupConfig struct { + BaseChat + InputMedia []interface{} +} + +func (config MediaGroupConfig) values() (url.Values, error) { + v, err := config.BaseChat.values() + if err != nil { + return v, err + } + + data, err := json.Marshal(config.InputMedia) + if err != nil { + return v, err + } + + v.Add("media", string(data)) + + return v, nil +} + +func (config MediaGroupConfig) method() string { + return "sendMediaGroup" +} + // LocationConfig contains information about a SendLocation request. type LocationConfig struct { BaseChat @@ -832,44 +898,20 @@ func (config EditMessageTextConfig) method() string { return "editMessageText" } -// EditMessageMediaConfig allows you to modify the text in a message. -type EditMessageMediaConfig struct { - BaseEdit - Media interface{} `json:"media"` - ParseMode string - DisableWebPagePreview bool -} - -func (config EditMessageMediaConfig) values() (url.Values, error) { - v, err := config.BaseEdit.values() - if err != nil { - return v, err - } - bytes, err := json.Marshal(config.Media) - if err != nil { - return v, err - } - v.Add("media", string(bytes)) - v.Add("parse_mode", config.ParseMode) - v.Add("disable_web_page_preview", strconv.FormatBool(config.DisableWebPagePreview)) - - return v, nil -} - -func (config EditMessageMediaConfig) method() string { - return "editMessageMedia" -} - // EditMessageCaptionConfig allows you to modify the caption of a message. type EditMessageCaptionConfig struct { BaseEdit - Caption string + Caption string + ParseMode string } func (config EditMessageCaptionConfig) values() (url.Values, error) { v, _ := config.BaseEdit.values() v.Add("caption", config.Caption) + if config.ParseMode != "" { + v.Add("parse_mode", config.ParseMode) + } return v, nil } @@ -892,6 +934,59 @@ func (config EditMessageReplyMarkupConfig) method() string { return "editMessageReplyMarkup" } +// EditMessageMediaConfig allows you to modify the media +// of a message. +type EditMessageMediaConfig struct { + BaseFile + BaseEdit + Media interface{} `json:"media"` + ParseMode string + DisableWebPagePreview bool +} + +func (config EditMessageMediaConfig) values() (url.Values, error) { + v, err := config.BaseChat.values() + if err != nil { + return v, err + } + v, err = config.BaseEdit.values() + if err != nil { + return v, err + } + if !config.UseExisting { + fileName := "" + switch mType := config.Media.(type) { + case InputMediaPhoto: + fileName = mType.Media + mType.Media = "attach://" + config.name() + config.Media = mType + case InputMediaVideo: + fileName = mType.Media + mType.Media = "attach://" + config.name() + config.Media = mType + } + config.File = fileName + } + + bytes, err := json.Marshal(config.Media) + if err != nil { + return v, err + } + v.Add("media", string(bytes)) + v.Add("parse_mode", config.ParseMode) + v.Add("disable_web_page_preview", strconv.FormatBool(config.DisableWebPagePreview)) + + return v, nil +} + +func (config EditMessageMediaConfig) name() string { + return "fileName" +} + +func (config EditMessageMediaConfig) method() string { + return "editMessageMedia" +} + // UserProfilePhotosConfig contains information about a // GetUserProfilePhotos request. type UserProfilePhotosConfig struct { @@ -1220,112 +1315,3 @@ func (config DeleteChatPhotoConfig) values() (url.Values, error) { return v, nil } - -// MediaGroupConfig allows you to send a group of media. -// -// Media consist of InputMedia items (InputMediaPhoto, InputMediaVideo). -type MediaGroupConfig struct { - ChatID int64 - ChannelUsername string - - Media []interface{} `json:"media"` - DisableNotification bool - ReplyToMessageID int -} - -func (config MediaGroupConfig) method() string { - return "sendMediaGroup" -} - -func (config MediaGroupConfig) values() (url.Values, error) { - v := url.Values{} - - if config.ChannelUsername == "" { - v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) - } else { - v.Add("chat_id", config.ChannelUsername) - } - bytes, err := json.Marshal(config.Media) - if err != nil { - return v, err - } - v.Add("media", string(bytes)) - if config.DisableNotification { - v.Add("disable_notification", strconv.FormatBool(config.DisableNotification)) - } - if config.ReplyToMessageID != 0 { - v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID)) - } - - return v, nil -} - -type InputMediaPhoto struct { - Type string `json:"type"` - Media string `json:"media"` - Caption string `json:"caption"` - ParseMode string `json:"parse_mode"` -} - -type InputMediaAudio struct { - Type string `json:"type"` - Media string `json:"media"` - Thumb string `json:"thumb,-"` - Caption string `json:"caption,-"` - ParseMode string `json:"parse_mode,-"` - Duration int `json:"duration,-"` - Performer string `json:"duration,-"` - Title string `json:"title,-"` -} - -type InputMediaVideo struct { - Type string `json:"type"` - Media string `json:"media"` - Caption string `json:"caption"` - ParseMode string `json:"parse_mode"` - Width int `json:"width"` - Height int `json:"height"` - Duration int `json:"duration"` - SupportsStreaming bool `json:"supports_streaming"` -} - -// CreateNewStickerSetConfig contains information about a CreateNewStickerSet -// request. -type CreateNewStickerSetConfig struct { - UserID int // required - Name string // required - Title string // required - PNGSticker interface{} // required - Emojis string // required - ContainsMasks bool - MaskPosition *MaskPosition -} - -// AddStickerToSetConfig contains information about a AddToStickerSet request. -type AddStickerToSetConfig struct { - UserID int // required - Name string // required - PNGSticker interface{} // required - Emojis string // required - MaskPosition *MaskPosition -} - -// SetStickerPositionInSetConfig contains information about a -// SetStickerPositionInSet request. -type SetStickerPositionInSetConfig struct { - Sticker string // required - Position int64 // required -} - -func (config SetStickerPositionInSetConfig) values() (url.Values, error) { - v := url.Values{} - - v.Add("sticker", config.Sticker) - v.Add("position", strconv.FormatInt(config.Position, 10)) - - return v, nil -} - -func (config SetStickerPositionInSetConfig) method() string { - return "setStickerPositionInSet" -} diff --git a/helpers.go b/helpers.go index f724cf8..0dab3c5 100644 --- a/helpers.go +++ b/helpers.go @@ -19,6 +19,7 @@ func NewMessage(chatID int64, text string) MessageConfig { } } +// NewDeleteMessage creates a request to delete a message. func NewDeleteMessage(chatID int64, messageID int) DeleteMessageConfig { return DeleteMessageConfig{ ChatID: chatID, @@ -51,22 +52,6 @@ func NewForward(chatID int64, fromChatID int64, messageID int) ForwardConfig { } } -// NewAnimationUpload creates a new animation uploader. -// -// chatID is where to send it, file is a string path to the file, -// FileReader, or FileBytes. -// -// Note that you must send animated GIFs as a document. -func NewAnimationUpload(chatID int64, file interface{}) AnimationConfig { - return AnimationConfig{ - BaseFile: BaseFile{ - BaseChat: BaseChat{ChatID: chatID}, - File: file, - UseExisting: false, - }, - } -} - // NewPhotoUpload creates a new photo uploader. // // chatID is where to send it, file is a string path to the file, @@ -98,17 +83,6 @@ func NewPhotoShare(chatID int64, fileID string) PhotoConfig { } } -// NewMediaGroupShare shares media group. -// -// chatID is where to send it, medias is the media interface -// already uploaded. -func NewMediaGroupShare(chatID int64, medias []interface{}) MediaGroupConfig { - return MediaGroupConfig{ - ChatID: chatID, - Media: medias, - } -} - // NewAudioUpload creates a new audio uploader. // // chatID is where to send it, file is a string path to the file, @@ -228,6 +202,35 @@ func NewVideoShare(chatID int64, fileID string) VideoConfig { } } +// NewAnimationUpload creates a new animation uploader. +// +// chatID is where to send it, file is a string path to the file, +// FileReader, or FileBytes. +func NewAnimationUpload(chatID int64, file interface{}) AnimationConfig { + return AnimationConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + File: file, + UseExisting: false, + }, + } +} + +// NewAnimationShare shares an existing animation. +// You may use this to reshare an existing animation without reuploading it. +// +// chatID is where to send it, fileID is the ID of the animation +// already uploaded. +func NewAnimationShare(chatID int64, fileID string) AnimationConfig { + return AnimationConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + FileID: fileID, + UseExisting: true, + }, + } +} + // NewVideoNoteUpload creates a new video note uploader. // // chatID is where to send it, file is a string path to the file, @@ -288,6 +291,33 @@ func NewVoiceShare(chatID int64, fileID string) VoiceConfig { } } +// NewMediaGroup creates a new media group. Files should be an array of +// two to ten InputMediaPhoto or InputMediaVideo. +func NewMediaGroup(chatID int64, files []interface{}) MediaGroupConfig { + return MediaGroupConfig{ + BaseChat: BaseChat{ + ChatID: chatID, + }, + InputMedia: files, + } +} + +// NewInputMediaPhoto creates a new InputMediaPhoto. +func NewInputMediaPhoto(media string) InputMediaPhoto { + return InputMediaPhoto{ + Type: "photo", + Media: media, + } +} + +// NewInputMediaVideo creates a new InputMediaVideo. +func NewInputMediaVideo(media string) InputMediaVideo { + return InputMediaVideo{ + Type: "video", + Media: media, + } +} + // NewContact allows you to send a shared contact. func NewContact(chatID int64, phoneNumber, firstName string) ContactConfig { return ContactConfig{ @@ -531,25 +561,56 @@ func NewEditMessageCaption(chatID int64, messageID int, caption string) EditMess } } -// NewEditMessageMedia allows you to edit the media of a message. +// NewEditMessageReplyMarkup allows you to edit the inline +// keyboard markup. +func NewEditMessageReplyMarkup(chatID int64, messageID int, replyMarkup InlineKeyboardMarkup) EditMessageReplyMarkupConfig { + return EditMessageReplyMarkupConfig{ + BaseEdit: BaseEdit{ + ChatID: chatID, + MessageID: messageID, + ReplyMarkup: &replyMarkup, + }, + } +} + +// NewEditMessageReplyMedia allows you to edit audio, document, photo, or video messages +// keyboard markup. func NewEditMessageMedia(chatID int64, messageID int, media interface{}) EditMessageMediaConfig { + fileName := "" + switch media.(type) { + case InputMediaPhoto: + photo := media.(InputMediaPhoto) + fileName = photo.Media + case InputMediaVideo: + video := media.(InputMediaVideo) + fileName = video.Media + } return EditMessageMediaConfig{ BaseEdit: BaseEdit{ ChatID: chatID, MessageID: messageID, }, Media: media, + BaseFile: BaseFile{ + FileID: fileName, + UseExisting: true, + }, } } -// NewEditMessageReplyMarkup allows you to edit the inline +// NewEditMessageReplyMedia allows you to edit audio, document, photo, or video messages // keyboard markup. -func NewEditMessageReplyMarkup(chatID int64, messageID int, replyMarkup InlineKeyboardMarkup) EditMessageReplyMarkupConfig { - return EditMessageReplyMarkupConfig{ +func NewEditMessageMediaUpload(chatID int64, messageID int, media interface{}) EditMessageMediaConfig { + return EditMessageMediaConfig{ BaseEdit: BaseEdit{ - ChatID: chatID, - MessageID: messageID, - ReplyMarkup: &replyMarkup, + ChatID: chatID, + MessageID: messageID, + }, + Media: media, + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + File: media, + UseExisting: false, }, } } diff --git a/passport.go b/passport.go new file mode 100644 index 0000000..5f55006 --- /dev/null +++ b/passport.go @@ -0,0 +1,315 @@ +package tgbotapi + +// PassportRequestInfoConfig allows you to request passport info +type PassportRequestInfoConfig struct { + BotID int `json:"bot_id"` + Scope *PassportScope `json:"scope"` + Nonce string `json:"nonce"` + PublicKey string `json:"public_key"` +} + +// PassportScopeElement supports using one or one of several elements. +type PassportScopeElement interface { + ScopeType() string +} + +// PassportScope is the requested scopes of data. +type PassportScope struct { + V int `json:"v"` + Data []PassportScopeElement `json:"data"` +} + +// PassportScopeElementOneOfSeveral allows you to request any one of the +// requested documents. +type PassportScopeElementOneOfSeveral struct { +} + +// ScopeType is the scope type. +func (eo *PassportScopeElementOneOfSeveral) ScopeType() string { + return "one_of" +} + +// PassportScopeElementOne requires the specified element be provided. +type PassportScopeElementOne struct { + Type string `json:"type"` // One of “personal_details”, “passport”, “driver_license”, “identity_card”, “internal_passport”, “address”, “utility_bill”, “bank_statement”, “rental_agreement”, “passport_registration”, “temporary_registration”, “phone_number”, “email” + Selfie bool `json:"selfie"` + Translation bool `json:"translation"` + NativeNames bool `json:"native_name"` +} + +// ScopeType is the scope type. +func (eo *PassportScopeElementOne) ScopeType() string { + return "one" +} + +type ( + // PassportData contains information about Telegram Passport data shared with + // the bot by the user. + PassportData struct { + // Array with information about documents and other Telegram Passport + // elements that was shared with the bot + Data []EncryptedPassportElement `json:"data"` + + // Encrypted credentials required to decrypt the data + Credentials *EncryptedCredentials `json:"credentials"` + } + + // PassportFile represents a file uploaded to Telegram Passport. Currently all + // Telegram Passport files are in JPEG format when decrypted and don't exceed + // 10MB. + PassportFile struct { + // Unique identifier for this file + FileID string `json:"file_id"` + + // File size + FileSize int `json:"file_size"` + + // Unix time when the file was uploaded + FileDate int64 `json:"file_date"` + } + + // EncryptedPassportElement contains information about documents or other + // Telegram Passport elements shared with the bot by the user. + EncryptedPassportElement struct { + // Element type. + Type string `json:"type"` + + // Base64-encoded encrypted Telegram Passport element data provided by + // the user, available for "personal_details", "passport", + // "driver_license", "identity_card", "identity_passport" and "address" + // types. Can be decrypted and verified using the accompanying + // EncryptedCredentials. + Data string `json:"data,omitempty"` + + // User's verified phone number, available only for "phone_number" type + PhoneNumber string `json:"phone_number,omitempty"` + + // User's verified email address, available only for "email" type + Email string `json:"email,omitempty"` + + // Array of encrypted files with documents provided by the user, + // available for "utility_bill", "bank_statement", "rental_agreement", + // "passport_registration" and "temporary_registration" types. Files can + // be decrypted and verified using the accompanying EncryptedCredentials. + Files []PassportFile `json:"files,omitempty"` + + // Encrypted file with the front side of the document, provided by the + // user. Available for "passport", "driver_license", "identity_card" and + // "internal_passport". The file can be decrypted and verified using the + // accompanying EncryptedCredentials. + FrontSide *PassportFile `json:"front_side,omitempty"` + + // Encrypted file with the reverse side of the document, provided by the + // user. Available for "driver_license" and "identity_card". The file can + // be decrypted and verified using the accompanying EncryptedCredentials. + ReverseSide *PassportFile `json:"reverse_side,omitempty"` + + // Encrypted file with the selfie of the user holding a document, + // provided by the user; available for "passport", "driver_license", + // "identity_card" and "internal_passport". The file can be decrypted + // and verified using the accompanying EncryptedCredentials. + Selfie *PassportFile `json:"selfie,omitempty"` + } + + // EncryptedCredentials contains data required for decrypting and + // authenticating EncryptedPassportElement. See the Telegram Passport + // Documentation for a complete description of the data decryption and + // authentication processes. + EncryptedCredentials struct { + // Base64-encoded encrypted JSON-serialized data with unique user's + // payload, data hashes and secrets required for EncryptedPassportElement + // decryption and authentication + Data string `json:"data"` + + // Base64-encoded data hash for data authentication + Hash string `json:"hash"` + + // Base64-encoded secret, encrypted with the bot's public RSA key, + // required for data decryption + Secret string `json:"secret"` + } + + // PassportElementError represents an error in the Telegram Passport element + // which was submitted that should be resolved by the user. + PassportElementError interface{} + + // PassportElementErrorDataField represents an issue in one of the data + // fields that was provided by the user. The error is considered resolved + // when the field's value changes. + PassportElementErrorDataField struct { + // Error source, must be data + Source string `json:"source"` + + // The section of the user's Telegram Passport which has the error, one + // of "personal_details", "passport", "driver_license", "identity_card", + // "internal_passport", "address" + Type string `json:"type"` + + // Name of the data field which has the error + FieldName string `json:"field_name"` + + // Base64-encoded data hash + DataHash string `json:"data_hash"` + + // Error message + Message string `json:"message"` + } + + // PassportElementErrorFrontSide represents an issue with the front side of + // a document. The error is considered resolved when the file with the front + // side of the document changes. + PassportElementErrorFrontSide struct { + // Error source, must be front_side + Source string `json:"source"` + + // The section of the user's Telegram Passport which has the issue, one + // of "passport", "driver_license", "identity_card", "internal_passport" + Type string `json:"type"` + + // Base64-encoded hash of the file with the front side of the document + FileHash string `json:"file_hash"` + + // Error message + Message string `json:"message"` + } + + // PassportElementErrorReverseSide represents an issue with the reverse side + // of a document. The error is considered resolved when the file with reverse + // side of the document changes. + PassportElementErrorReverseSide struct { + // Error source, must be reverse_side + Source string `json:"source"` + + // The section of the user's Telegram Passport which has the issue, one + // of "driver_license", "identity_card" + Type string `json:"type"` + + // Base64-encoded hash of the file with the reverse side of the document + FileHash string `json:"file_hash"` + + // Error message + Message string `json:"message"` + } + + // PassportElementErrorSelfie represents an issue with the selfie with a + // document. The error is considered resolved when the file with the selfie + // changes. + PassportElementErrorSelfie struct { + // Error source, must be selfie + Source string `json:"source"` + + // The section of the user's Telegram Passport which has the issue, one + // of "passport", "driver_license", "identity_card", "internal_passport" + Type string `json:"type"` + + // Base64-encoded hash of the file with the selfie + FileHash string `json:"file_hash"` + + // Error message + Message string `json:"message"` + } + + // PassportElementErrorFile represents an issue with a document scan. The + // error is considered resolved when the file with the document scan changes. + PassportElementErrorFile struct { + // Error source, must be file + Source string `json:"source"` + + // The section of the user's Telegram Passport which has the issue, one + // of "utility_bill", "bank_statement", "rental_agreement", + // "passport_registration", "temporary_registration" + Type string `json:"type"` + + // Base64-encoded file hash + FileHash string `json:"file_hash"` + + // Error message + Message string `json:"message"` + } + + // PassportElementErrorFiles represents an issue with a list of scans. The + // error is considered resolved when the list of files containing the scans + // changes. + PassportElementErrorFiles struct { + // Error source, must be files + Source string `json:"source"` + + // The section of the user's Telegram Passport which has the issue, one + // of "utility_bill", "bank_statement", "rental_agreement", + // "passport_registration", "temporary_registration" + Type string `json:"type"` + + // List of base64-encoded file hashes + FileHashes []string `json:"file_hashes"` + + // Error message + Message string `json:"message"` + } + + // Credentials contains encrypted data. + Credentials struct { + Data SecureData `json:"secure_data"` + // Nonce the same nonce given in the request + Nonce string `json:"nonce"` + } + + // SecureData is a map of the fields and their encrypted values. + SecureData map[string]*SecureValue + // PersonalDetails *SecureValue `json:"personal_details"` + // Passport *SecureValue `json:"passport"` + // InternalPassport *SecureValue `json:"internal_passport"` + // DriverLicense *SecureValue `json:"driver_license"` + // IdentityCard *SecureValue `json:"identity_card"` + // Address *SecureValue `json:"address"` + // UtilityBill *SecureValue `json:"utility_bill"` + // BankStatement *SecureValue `json:"bank_statement"` + // RentalAgreement *SecureValue `json:"rental_agreement"` + // PassportRegistration *SecureValue `json:"passport_registration"` + // TemporaryRegistration *SecureValue `json:"temporary_registration"` + + // SecureValue contains encrypted values for a SecureData item. + SecureValue struct { + Data *DataCredentials `json:"data"` + FrontSide *FileCredentials `json:"front_side"` + ReverseSide *FileCredentials `json:"reverse_side"` + Selfie *FileCredentials `json:"selfie"` + Translation []*FileCredentials `json:"translation"` + Files []*FileCredentials `json:"files"` + } + + // DataCredentials contains information required to decrypt data. + DataCredentials struct { + // DataHash checksum of encrypted data + DataHash string `json:"data_hash"` + // Secret of encrypted data + Secret string `json:"secret"` + } + + // FileCredentials contains information required to decrypt files. + FileCredentials struct { + // FileHash checksum of encrypted data + FileHash string `json:"file_hash"` + // Secret of encrypted data + Secret string `json:"secret"` + } + + // PersonalDetails https://core.telegram.org/passport#personaldetails + PersonalDetails struct { + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + MiddleName string `json:"middle_name"` + BirthDate string `json:"birth_date"` + Gender string `json:"gender"` + CountryCode string `json:"country_code"` + ResidenceCountryCode string `json:"residence_country_code"` + FirstNameNative string `json:"first_name_native"` + LastNameNative string `json:"last_name_native"` + MiddleNameNative string `json:"middle_name_native"` + } + + // IDDocumentData https://core.telegram.org/passport#iddocumentdata + IDDocumentData struct { + DocumentNumber string `json:"document_no"` + ExpiryDate string `json:"expiry_date"` + } +) diff --git a/types.go b/types.go index d7a29bc..d3c433f 100644 --- a/types.go +++ b/types.go @@ -142,10 +142,9 @@ type Message struct { EditDate int `json:"edit_date"` // optional Text string `json:"text"` // optional Entities *[]MessageEntity `json:"entities"` // optional - CaptionEntities *[]MessageEntity `json:"caption_entities"` // optional - Animation *Animation `json:"animation"` // optional Audio *Audio `json:"audio"` // optional Document *Document `json:"document"` // optional + Animation *ChatAnimation `json:"animation"` // optional Game *Game `json:"game"` // optional Photo *[]PhotoSize `json:"photo"` // optional Sticker *Sticker `json:"sticker"` // optional @@ -169,6 +168,7 @@ type Message struct { PinnedMessage *Message `json:"pinned_message"` // optional Invoice *Invoice `json:"invoice"` // optional SuccessfulPayment *SuccessfulPayment `json:"successful_payment"` // optional + PassportData *PassportData `json:"passport_data,omitempty"` // optional } // Time converts the message timestamp into a Time. @@ -178,11 +178,7 @@ func (m *Message) Time() time.Time { // IsCommand returns true if message starts with a "bot_command" entity. func (m *Message) IsCommand() bool { - lenEntities := 0 - if m.Entities != nil { - lenEntities = len(*m.Entities) - } - if m.Entities == nil || lenEntities == 0 { + if m.Entities == nil || len(*m.Entities) == 0 { return false } @@ -288,6 +284,29 @@ type Document struct { FileSize int `json:"file_size"` // optional } +// Sticker contains information about a sticker. +type Sticker struct { + FileID string `json:"file_id"` + Width int `json:"width"` + Height int `json:"height"` + Thumbnail *PhotoSize `json:"thumb"` // optional + Emoji string `json:"emoji"` // optional + FileSize int `json:"file_size"` // optional + SetName string `json:"set_name"` // optional +} + +// ChatAnimation contains information about an animation. +type ChatAnimation struct { + FileID string `json:"file_id"` + Width int `json:"width"` + Height int `json:"height"` + Duration int `json:"duration"` + Thumbnail *PhotoSize `json:"thumb"` // optional + FileName string `json:"file_name"` // optional + MimeType string `json:"mime_type"` // optional + FileSize int `json:"file_size"` // optional +} + // Video contains information about a video. type Video struct { FileID string `json:"file_id"` @@ -506,6 +525,27 @@ func (info WebhookInfo) IsSet() bool { return info.URL != "" } +// InputMediaPhoto contains a photo for displaying as part of a media group. +type InputMediaPhoto struct { + Type string `json:"type"` + Media string `json:"media"` + Caption string `json:"caption"` + ParseMode string `json:"parse_mode"` +} + +// InputMediaVideo contains a video for displaying as part of a media group. +type InputMediaVideo struct { + Type string `json:"type"` + Media string `json:"media"` + // thumb intentionally missing as it is not currently compatible + Caption string `json:"caption"` + ParseMode string `json:"parse_mode"` + Width int `json:"width"` + Height int `json:"height"` + Duration int `json:"duration"` + SupportsStreaming bool `json:"supports_streaming"` +} + // InlineQuery is a Query from Telegram for an inline request. type InlineQuery struct { ID string `json:"id"` @@ -768,34 +808,6 @@ type PreCheckoutQuery struct { OrderInfo *OrderInfo `json:"order_info,omitempty"` } -// Sticker contains information about a sticker. -type Sticker struct { - FileID string `json:"file_id"` - Width int `json:"width"` - Height int `json:"height"` - Thumbnail *PhotoSize `json:"thumb"` // optional - Emoji string `json:"emoji"` // optional - SetName string `json:"set_name"` // optional - MaskPosition *MaskPosition `json:"mask_position"` // optional - FileSize int `json:"file_size"` // optional -} - -// StickerSet represents a sticker set. -type StickerSet struct { - Name string `json:"name"` - Title string `json:"title"` - ContainsMasks bool `json:"contains_masks"` - Stickers *[]Sticker `json:"stickers"` -} - -// MaskPosition describes the position on faces where a mask should be placed by default. -type MaskPosition struct { - Point string `json:"point"` - XShift float64 `json:"x_shift"` - YShift float64 `json:"y_shift"` - scale float64 `json:"scale"` -} - // Error is an error containing extra information returned by the Telegram API. type Error struct { Message string