diff --git a/bot.go b/bot.go index c6ca084..5399d96 100644 --- a/bot.go +++ b/bot.go @@ -27,6 +27,8 @@ type BotAPI struct { Self User `json:"-"` Client *http.Client `json:"-"` shutdownChannel chan interface{} + + apiEndpoint string } // NewBotAPI creates a new BotAPI instance. @@ -46,6 +48,8 @@ func NewBotAPIWithClient(token string, client *http.Client) (*BotAPI, error) { Client: client, Buffer: 100, shutdownChannel: make(chan interface{}), + + apiEndpoint: APIEndpoint, } self, err := bot.GetMe() @@ -58,6 +62,10 @@ func NewBotAPIWithClient(token string, client *http.Client) (*BotAPI, error) { return bot, nil } +func (b *BotAPI) SetAPIEndpoint(apiEndpoint string) { + b.apiEndpoint = apiEndpoint +} + func buildParams(in Params) (out url.Values) { if in == nil { return url.Values{} @@ -78,7 +86,7 @@ func (bot *BotAPI) MakeRequest(endpoint string, params Params) (APIResponse, err log.Printf("Endpoint: %s, params: %v\n", endpoint, params) } - method := fmt.Sprintf(APIEndpoint, bot.Token, endpoint) + method := fmt.Sprintf(bot.apiEndpoint, bot.Token, endpoint) values := buildParams(params) @@ -106,6 +114,7 @@ func (bot *BotAPI) MakeRequest(endpoint string, params Params) (APIResponse, err } return apiResp, Error{ + Code: apiResp.ErrorCode, Message: apiResp.Description, ResponseParameters: parameters, } @@ -199,7 +208,7 @@ func (bot *BotAPI) UploadFile(endpoint string, params Params, fieldname string, log.Printf("Endpoint: %s, fieldname: %s, params: %v, file: %T\n", endpoint, fieldname, params, file) } - method := fmt.Sprintf(APIEndpoint, bot.Token, endpoint) + method := fmt.Sprintf(bot.apiEndpoint, bot.Token, endpoint) req, err := http.NewRequest("POST", method, nil) if err != nil { @@ -439,6 +448,7 @@ func (bot *BotAPI) ListenForWebhook(pattern string) UpdatesChannel { 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) diff --git a/go.mod b/go.mod index 42f56cf..c17fcfa 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,8 @@ module github.com/go-telegram-bot-api/telegram-bot-api/v5 -require github.com/technoweenie/multipartstreamer v1.0.1 +require ( + github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect + github.com/technoweenie/multipartstreamer v1.0.1 +) go 1.13 diff --git a/go.sum b/go.sum index 8660600..8a78530 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ +github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU= +github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM= github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM= github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= diff --git a/helpers.go b/helpers.go index 2a6e787..434f87b 100644 --- a/helpers.go +++ b/helpers.go @@ -491,6 +491,15 @@ func NewInlineQueryResultGIF(id, url string) InlineQueryResultGIF { } } +// NewInlineQueryResultCachedGIF create a new inline query with cached photo. +func NewInlineQueryResultCachedGIF(id, gifID string) InlineQueryResultCachedGIF { + return InlineQueryResultCachedGIF{ + Type: "gif", + ID: id, + GifID: gifID, + } +} + // NewInlineQueryResultMPEG4GIF creates a new inline query MPEG4 GIF. func NewInlineQueryResultMPEG4GIF(id, url string) InlineQueryResultMPEG4GIF { return InlineQueryResultMPEG4GIF{ @@ -500,6 +509,15 @@ func NewInlineQueryResultMPEG4GIF(id, url string) InlineQueryResultMPEG4GIF { } } +// NewInlineQueryResultCachedPhoto create a new inline query with cached photo. +func NewInlineQueryResultCachedMPEG4GIF(id, MPEG4GifID string) InlineQueryResultCachedMpeg4Gif { + return InlineQueryResultCachedMpeg4Gif{ + Type: "mpeg4_gif", + ID: id, + MGifID: MPEG4GifID, + } +} + // NewInlineQueryResultPhoto creates a new inline query photo. func NewInlineQueryResultPhoto(id, url string) InlineQueryResultPhoto { return InlineQueryResultPhoto{ @@ -519,6 +537,15 @@ func NewInlineQueryResultPhotoWithThumb(id, url, thumb string) InlineQueryResult } } +// NewInlineQueryResultCachedPhoto create a new inline query with cached photo. +func NewInlineQueryResultCachedPhoto(id, photoID string) InlineQueryResultCachedPhoto { + return InlineQueryResultCachedPhoto{ + Type: "photo", + ID: id, + PhotoID: photoID, + } +} + // NewInlineQueryResultVideo creates a new inline query video. func NewInlineQueryResultVideo(id, url string) InlineQueryResultVideo { return InlineQueryResultVideo{ @@ -528,6 +555,16 @@ func NewInlineQueryResultVideo(id, url string) InlineQueryResultVideo { } } +// NewInlineQueryResultCachedVideo create a new inline query with cached video. +func NewInlineQueryResultCachedVideo(id, videoID, title string) InlineQueryResultCachedVideo { + return InlineQueryResultCachedVideo{ + Type: "video", + ID: id, + VideoID: videoID, + Title: title, + } +} + // NewInlineQueryResultAudio creates a new inline query audio. func NewInlineQueryResultAudio(id, url, title string) InlineQueryResultAudio { return InlineQueryResultAudio{ @@ -538,6 +575,15 @@ func NewInlineQueryResultAudio(id, url, title string) InlineQueryResultAudio { } } +// NewInlineQueryResultCachedAudio create a new inline query with cached photo. +func NewInlineQueryResultCachedAudio(id, audioID string) InlineQueryResultCachedAudio { + return InlineQueryResultCachedAudio{ + Type: "audio", + ID: id, + AudioID: audioID, + } +} + // NewInlineQueryResultVoice creates a new inline query voice. func NewInlineQueryResultVoice(id, url, title string) InlineQueryResultVoice { return InlineQueryResultVoice{ @@ -548,6 +594,16 @@ func NewInlineQueryResultVoice(id, url, title string) InlineQueryResultVoice { } } +// NewInlineQueryResultCachedVoice create a new inline query with cached photo. +func NewInlineQueryResultCachedVoice(id, voiceID, title string) InlineQueryResultCachedVoice { + return InlineQueryResultCachedVoice{ + Type: "voice", + ID: id, + VoiceID: voiceID, + Title: title, + } +} + // NewInlineQueryResultDocument creates a new inline query document. func NewInlineQueryResultDocument(id, url, title, mimeType string) InlineQueryResultDocument { return InlineQueryResultDocument{ @@ -559,6 +615,16 @@ func NewInlineQueryResultDocument(id, url, title, mimeType string) InlineQueryRe } } +// NewInlineQueryResultCachedDocument create a new inline query with cached photo. +func NewInlineQueryResultCachedDocument(id, documentID, title string) InlineQueryResultCachedDocument { + return InlineQueryResultCachedDocument{ + Type: "document", + ID: id, + DocumentID: documentID, + Title: title, + } +} + // NewInlineQueryResultLocation creates a new inline query location. func NewInlineQueryResultLocation(id, title string, latitude, longitude float64) InlineQueryResultLocation { return InlineQueryResultLocation{ @@ -570,6 +636,18 @@ func NewInlineQueryResultLocation(id, title string, latitude, longitude float64) } } +// NewInlineQueryResultVenue creates a new inline query venue. +func NewInlineQueryResultVenue(id, title, address string, latitude, longitude float64) InlineQueryResultVenue { + return InlineQueryResultVenue{ + Type: "venue", + ID: id, + Title: title, + Address: address, + Latitude: latitude, + Longitude: longitude, + } +} + // NewEditMessageText allows you to edit the text of a message. func NewEditMessageText(chatID int64, messageID int, text string) EditMessageTextConfig { return EditMessageTextConfig{ diff --git a/types.go b/types.go index 13c4229..210509c 100644 --- a/types.go +++ b/types.go @@ -216,7 +216,7 @@ func (m *Message) IsCommand() bool { } entity := m.Entities[0] - return entity.Offset == 0 && entity.Type == "bot_command" + return entity.Offset == 0 && entity.IsCommand() } // Command checks if the message was a command and if it was, returns the @@ -284,12 +284,62 @@ type MessageEntity struct { } // ParseURL attempts to parse a URL contained within a MessageEntity. -func (entity MessageEntity) ParseURL() (*url.URL, error) { - if entity.URL == "" { +func (e MessageEntity) ParseURL() (*url.URL, error) { + if e.URL == "" { return nil, errors.New(ErrBadURL) } - return url.Parse(entity.URL) + return url.Parse(e.URL) +} + +// IsMention returns true if the type of the message entity is "mention" (@username). +func (e MessageEntity) IsMention() bool { + return e.Type == "mention" +} + +// IsHashtag returns true if the type of the message entity is "hashtag". +func (e MessageEntity) IsHashtag() bool { + return e.Type == "hashtag" +} + +// IsCommand returns true if the type of the message entity is "bot_command". +func (e MessageEntity) IsCommand() bool { + return e.Type == "bot_command" +} + +// IsUrl returns true if the type of the message entity is "url". +func (e MessageEntity) IsUrl() bool { + return e.Type == "url" +} + +// IsEmail returns true if the type of the message entity is "email". +func (e MessageEntity) IsEmail() bool { + return e.Type == "email" +} + +// IsBold returns true if the type of the message entity is "bold" (bold text). +func (e MessageEntity) IsBold() bool { + return e.Type == "bold" +} + +// IsItalic returns true if the type of the message entity is "italic" (italic text). +func (e MessageEntity) IsItalic() bool { + return e.Type == "italic" +} + +// IsCode returns true if the type of the message entity is "code" (monowidth string). +func (e MessageEntity) IsCode() bool { + return e.Type == "code" +} + +// IsPre returns true if the type of the message entity is "pre" (monowidth block). +func (e MessageEntity) IsPre() bool { + return e.Type == "pre" +} + +// IsTextLink returns true if the type of the message entity is "text_link" (clickable text URL). +func (e MessageEntity) IsTextLink() bool { + return e.Type == "text_link" } // PhotoSize contains information about photos. @@ -671,17 +721,42 @@ type InlineQueryResultPhoto struct { InputMessageContent interface{} `json:"input_message_content,omitempty"` } +// InlineQueryResultCachedPhoto is an inline query response with cached photo. +type InlineQueryResultCachedPhoto struct { + Type string `json:"type"` // required + ID string `json:"id"` // required + PhotoID string `json:"photo_file_id"` // required + Title string `json:"title"` + Description string `json:"description"` + Caption string `json:"caption"` + ParseMode string `json:"parse_mode"` + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` + InputMessageContent interface{} `json:"input_message_content,omitempty"` +} + // InlineQueryResultGIF is an inline query response GIF. type InlineQueryResultGIF struct { - Type string `json:"type"` // required - ID string `json:"id"` // required - URL string `json:"gif_url"` // required - Width int `json:"gif_width"` - Height int `json:"gif_height"` - Duration int `json:"gif_duration"` - ThumbURL string `json:"thumb_url"` + Type string `json:"type"` // required + ID string `json:"id"` // required + URL string `json:"gif_url"` // required + ThumbURL string `json:"thumb_url"` // required + Width int `json:"gif_width,omitempty"` + Height int `json:"gif_height,omitempty"` + Duration int `json:"gif_duration,omitempty"` + Title string `json:"title,omitempty"` + Caption string `json:"caption,omitempty"` + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` + InputMessageContent interface{} `json:"input_message_content,omitempty"` +} + +// InlineQueryResultCachedGIF is an inline query response with cached gif. +type InlineQueryResultCachedGIF struct { + Type string `json:"type"` // required + ID string `json:"id"` // required + GifID string `json:"gif_file_id"` // required Title string `json:"title"` Caption string `json:"caption"` + ParseMode string `json:"parse_mode"` ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` InputMessageContent interface{} `json:"input_message_content,omitempty"` } @@ -701,6 +776,19 @@ type InlineQueryResultMPEG4GIF struct { InputMessageContent interface{} `json:"input_message_content,omitempty"` } +// InlineQueryResultCachedMpeg4Gif is an inline query response with cached +// H.264/MPEG-4 AVC video without sound gif. +type InlineQueryResultCachedMpeg4Gif struct { + Type string `json:"type"` // required + ID string `json:"id"` // required + MGifID string `json:"mpeg4_file_id"` // required + Title string `json:"title"` + Caption string `json:"caption"` + ParseMode string `json:"parse_mode"` + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` + InputMessageContent interface{} `json:"input_message_content,omitempty"` +} + // InlineQueryResultVideo is an inline query response video. type InlineQueryResultVideo struct { Type string `json:"type"` // required @@ -718,6 +806,19 @@ type InlineQueryResultVideo struct { InputMessageContent interface{} `json:"input_message_content,omitempty"` } +// InlineQueryResultCachedVideo is an inline query response with cached video. +type InlineQueryResultCachedVideo struct { + Type string `json:"type"` // required + ID string `json:"id"` // required + VideoID string `json:"video_file_id"` // required + Title string `json:"title"` // required + Description string `json:"description"` + Caption string `json:"caption"` + ParseMode string `json:"parse_mode"` + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` + InputMessageContent interface{} `json:"input_message_content,omitempty"` +} + // InlineQueryResultAudio is an inline query response audio. type InlineQueryResultAudio struct { Type string `json:"type"` // required @@ -731,6 +832,17 @@ type InlineQueryResultAudio struct { InputMessageContent interface{} `json:"input_message_content,omitempty"` } +// InlineQueryResultCachedAudio is an inline query response with cached audio. +type InlineQueryResultCachedAudio struct { + Type string `json:"type"` // required + ID string `json:"id"` // required + AudioID string `json:"audio_file_id"` // required + Caption string `json:"caption"` + ParseMode string `json:"parse_mode"` + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` + InputMessageContent interface{} `json:"input_message_content,omitempty"` +} + // InlineQueryResultVoice is an inline query response voice. type InlineQueryResultVoice struct { Type string `json:"type"` // required @@ -743,6 +855,18 @@ type InlineQueryResultVoice struct { InputMessageContent interface{} `json:"input_message_content,omitempty"` } +// InlineQueryResultCachedVoice is an inline query response with cached voice. +type InlineQueryResultCachedVoice struct { + Type string `json:"type"` // required + ID string `json:"id"` // required + VoiceID string `json:"voice_file_id"` // required + Title string `json:"title"` // required + Caption string `json:"caption"` + ParseMode string `json:"parse_mode"` + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` + InputMessageContent interface{} `json:"input_message_content,omitempty"` +} + // InlineQueryResultDocument is an inline query response document. type InlineQueryResultDocument struct { Type string `json:"type"` // required @@ -759,6 +883,19 @@ type InlineQueryResultDocument struct { ThumbHeight int `json:"thumb_height"` } +// InlineQueryResultCachedDocument is an inline query response with cached document. +type InlineQueryResultCachedDocument struct { + Type string `json:"type"` // required + ID string `json:"id"` // required + DocumentID string `json:"document_file_id"` // required + Title string `json:"title"` // required + Caption string `json:"caption"` + Description string `json:"description"` + ParseMode string `json:"parse_mode"` + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` + InputMessageContent interface{} `json:"input_message_content,omitempty"` +} + // InlineQueryResultLocation is an inline query response location. type InlineQueryResultLocation struct { Type string `json:"type"` // required @@ -789,6 +926,23 @@ type InlineQueryResultContact struct { ThumbHeight int `json:"thumb_height"` } +// InlineQueryResultVenue is an inline query response venue. +type InlineQueryResultVenue struct { + Type string `json:"type"` // required + ID string `json:"id"` // required + Latitude float64 `json:"latitude"` // required + Longitude float64 `json:"longitude"` // required + Title string `json:"title"` // required + Address string `json:"address"` // required + FoursquareID string `json:"foursquare_id"` + FoursquareType string `json:"foursquare_type"` + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` + InputMessageContent interface{} `json:"input_message_content,omitempty"` + ThumbURL string `json:"thumb_url"` + ThumbWidth int `json:"thumb_width"` + ThumbHeight int `json:"thumb_height"` +} + // InlineQueryResultGame is an inline query response game. type InlineQueryResultGame struct { Type string `json:"type"` @@ -964,6 +1118,7 @@ type InputMediaDocument struct { // Error is an error containing extra information returned by the Telegram API. type Error struct { + Code int Message string ResponseParameters } diff --git a/types_test.go b/types_test.go index f811ba7..0e8ed14 100644 --- a/types_test.go +++ b/types_test.go @@ -189,6 +189,86 @@ func TestChatIsSuperGroup(t *testing.T) { } } +func TestMessageEntityIsMention(t *testing.T) { + entity := MessageEntity{Type: "mention"} + + if !entity.IsMention() { + t.Fail() + } +} + +func TestMessageEntityIsHashtag(t *testing.T) { + entity := MessageEntity{Type: "hashtag"} + + if !entity.IsHashtag() { + t.Fail() + } +} + +func TestMessageEntityIsBotCommand(t *testing.T) { + entity := MessageEntity{Type: "bot_command"} + + if !entity.IsCommand() { + t.Fail() + } +} + +func TestMessageEntityIsUrl(t *testing.T) { + entity := MessageEntity{Type: "url"} + + if !entity.IsUrl() { + t.Fail() + } +} + +func TestMessageEntityIsEmail(t *testing.T) { + entity := MessageEntity{Type: "email"} + + if !entity.IsEmail() { + t.Fail() + } +} + +func TestMessageEntityIsBold(t *testing.T) { + entity := MessageEntity{Type: "bold"} + + if !entity.IsBold() { + t.Fail() + } +} + +func TestMessageEntityIsItalic(t *testing.T) { + entity := MessageEntity{Type: "italic"} + + if !entity.IsItalic() { + t.Fail() + } +} + +func TestMessageEntityIsCode(t *testing.T) { + entity := MessageEntity{Type: "code"} + + if !entity.IsCode() { + t.Fail() + } +} + +func TestMessageEntityIsPre(t *testing.T) { + entity := MessageEntity{Type: "pre"} + + if !entity.IsPre() { + t.Fail() + } +} + +func TestMessageEntityIsTextLink(t *testing.T) { + entity := MessageEntity{Type: "text_link"} + + if !entity.IsTextLink() { + t.Fail() + } +} + func TestFileLink(t *testing.T) { file := File{FilePath: "test/test.txt"}