diff --git a/README.md b/README.md index e997939..b42ac69 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,16 @@ func main() { log.Fatal(err) } + info, err := bot.GetWebhookInfo() + + if err != nil { + log.Fatal(err) + } + + if info.LastErrorDate != 0 { + log.Printf("failed to set webhook: %s", info.LastErrorMessage) + } + updates := bot.ListenForWebhook("/" + bot.Token) go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil) diff --git a/bot.go b/bot.go index 63fb3e0..fde5b50 100644 --- a/bot.go +++ b/bot.go @@ -7,6 +7,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "io/ioutil" "log" "net/http" @@ -71,28 +72,56 @@ func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse, } defer resp.Body.Close() - bytes, err := ioutil.ReadAll(resp.Body) + var apiResp APIResponse + bytes, err := bot.decodeAPIResponse(resp.Body, &apiResp) if err != nil { - return APIResponse{}, err + return apiResp, err } if bot.Debug { log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes)) } - var apiResp APIResponse - err = json.Unmarshal(bytes, &apiResp) - if err != nil { - return APIResponse{}, err - } - if !apiResp.Ok { - return apiResp, errors.New(apiResp.Description) + var parameters ResponseParameters + + if apiResp.Parameters != nil { + parameters = *apiResp.Parameters + } + + return apiResp, Error{ + Message: apiResp.Description, + ResponseParameters: parameters, + } } return apiResp, nil } +// decodeAPIResponse decode response and return slice of bytes if debug enabled. +// If debug disabled, just decode http.Response.Body stream to APIResponse struct +// for efficient memory usage +func (bot *BotAPI) decodeAPIResponse(responseBody io.Reader, resp *APIResponse) (_ []byte, err error) { + if !bot.Debug { + dec := json.NewDecoder(responseBody) + err = dec.Decode(resp) + return + } + + // if debug, read reponse body + data, err := ioutil.ReadAll(responseBody) + if err != nil { + return + } + + err = json.Unmarshal(data, resp) + if err != nil { + return + } + + return data, nil +} + // UploadFile makes a request to the API with a file. // // Requires the parameter to hold the file not be in the params. @@ -543,3 +572,45 @@ func (bot *BotAPI) GetStickerSet(config GetStickerSetConfig) (StickerSet, error) return stickers, err } + +// SetChatTitle change title of chat. +func (bot *BotAPI) SetChatTitle(config SetChatTitleConfig) (APIResponse, error) { + v, err := config.values() + if err != nil { + return APIResponse{}, err + } + + return bot.MakeRequest(config.method(), v) +} + +// SetChatDescription change description of chat. +func (bot *BotAPI) SetChatDescription(config SetChatDescriptionConfig) (APIResponse, error) { + v, err := config.values() + if err != nil { + return APIResponse{}, err + } + + return bot.MakeRequest(config.method(), v) +} + +// SetChatPhoto change photo of chat. +func (bot *BotAPI) SetChatPhoto(config SetChatPhotoConfig) (APIResponse, error) { + params, err := config.params() + if err != nil { + return APIResponse{}, err + } + + file := config.getFile() + + return bot.UploadFile(config.method(), params, config.name(), file) +} + +// DeleteChatPhoto delete photo of chat. +func (bot *BotAPI) DeleteChatPhoto(config DeleteChatPhotoConfig) (APIResponse, error) { + v, err := config.values() + if err != nil { + return APIResponse{}, err + } + + return bot.MakeRequest(config.method(), v) +} diff --git a/bot_test.go b/bot_test.go index cb584dc..1727396 100644 --- a/bot_test.go +++ b/bot_test.go @@ -476,6 +476,16 @@ func TestSetWebhookWithCert(t *testing.T) { t.Fail() } + info, err := bot.GetWebhookInfo() + + if err != nil { + t.Error(err) + } + + if info.LastErrorDate != 0 { + t.Errorf("failed to set webhook: %s", info.LastErrorMessage) + } + bot.Request(tgbotapi.RemoveWebhookConfig{}) } @@ -493,6 +503,16 @@ func TestSetWebhookWithoutCert(t *testing.T) { t.Fail() } + info, err := bot.GetWebhookInfo() + + if err != nil { + t.Error(err) + } + + if info.LastErrorDate != 0 { + t.Errorf("failed to set webhook: %s", info.LastErrorMessage) + } + bot.Request(tgbotapi.RemoveWebhookConfig{}) } @@ -558,6 +578,16 @@ func ExampleNewWebhook() { log.Fatal(err) } + info, err := bot.GetWebhookInfo() + + if err != nil { + log.Fatal(err) + } + + if info.LastErrorDate != 0 { + log.Printf("failed to set webhook: %s", info.LastErrorMessage) + } + updates := bot.ListenForWebhook("/" + bot.Token) go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil) @@ -566,7 +596,7 @@ func ExampleNewWebhook() { } } -func ExampleAnswerInlineQuery() { +func ExampleInlineConfig() { bot, err := tgbotapi.NewBotAPI("MyAwesomeBotToken") // create new bot if err != nil { log.Panic(err) diff --git a/configs.go b/configs.go index bfbf0ca..e8c4514 100644 --- a/configs.go +++ b/configs.go @@ -720,7 +720,7 @@ type SetGameScoreConfig struct { Score int Force bool DisableEditMessage bool - ChatID int + ChatID int64 ChannelUsername string MessageID int InlineMessageID string @@ -733,7 +733,7 @@ func (config SetGameScoreConfig) values() (url.Values, error) { v.Add("score", strconv.Itoa(config.Score)) if config.InlineMessageID == "" { if config.ChannelUsername == "" { - v.Add("chat_id", strconv.Itoa(config.ChatID)) + v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) } else { v.Add("chat_id", config.ChannelUsername) } @@ -1376,10 +1376,7 @@ func (config UnpinChatMessageConfig) values() (url.Values, error) { // SetChatPhotoConfig allows you to set a group, supergroup, or channel's photo. type SetChatPhotoConfig struct { - ChatID int64 - ChannelUsername string - - Photo interface{} + BaseFile } func (config SetChatPhotoConfig) method() string { @@ -1390,36 +1387,12 @@ func (config SetChatPhotoConfig) name() string { return "photo" } -func (config SetChatPhotoConfig) 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) - } - - return v, nil -} - -func (config SetChatPhotoConfig) params() map[string]string { - params := make(map[string]string) - - if config.ChannelUsername == "" { - params["chat_id"] = strconv.FormatInt(config.ChatID, 10) - } else { - params["chat_id"] = config.ChannelUsername - } - - return params -} - func (config SetChatPhotoConfig) getFile() interface{} { - return config.Photo + return config.File } func (config SetChatPhotoConfig) useExistingFile() bool { - return false + return config.UseExisting } // DeleteChatPhotoConfig allows you to delete a group, supergroup, or channel's photo. diff --git a/helpers.go b/helpers.go index 132d957..c23a3bf 100644 --- a/helpers.go +++ b/helpers.go @@ -641,7 +641,7 @@ func NewCallbackWithAlert(id, text string) CallbackConfig { } } -// NewInvoice created a new Invoice request to the user. +// NewInvoice creates a new Invoice request to the user. func NewInvoice(chatID int64, title, description, payload, providerToken, startParameter, currency string, prices *[]LabeledPrice) InvoiceConfig { return InvoiceConfig{ BaseChat: BaseChat{ChatID: chatID}, @@ -653,3 +653,34 @@ func NewInvoice(chatID int64, title, description, payload, providerToken, startP Currency: currency, Prices: prices} } + +// NewSetChatPhotoUpload creates a new chat photo 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 NewSetChatPhotoUpload(chatID int64, file interface{}) SetChatPhotoConfig { + return SetChatPhotoConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + File: file, + UseExisting: false, + }, + } +} + +// NewSetChatPhotoShare shares an existing photo. +// You may use this to reshare an existing photo without reuploading it. +// +// chatID is where to send it, fileID is the ID of the file +// already uploaded. +func NewSetChatPhotoShare(chatID int64, fileID string) SetChatPhotoConfig { + return SetChatPhotoConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + FileID: fileID, + UseExisting: true, + }, + } +} diff --git a/types.go b/types.go index c8f75a2..d0ddcd5 100644 --- a/types.go +++ b/types.go @@ -676,7 +676,7 @@ type InlineQueryResultGame struct { Type string `json:"type"` ID string `json:"id"` GameShortName string `json:"game_short_name"` - ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup"` + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` } // ChosenInlineResult is an inline query result chosen by a User @@ -819,3 +819,14 @@ type InputMediaVideo struct { Height int `json:"height,omitempty"` Duration int `json:"duration,omitempty"` } + +// Error is an error containing extra information returned by the Telegram API. +type Error struct { + Message string + ResponseParameters +} + +// Error message string. +func (e Error) Error() string { + return e.Message +}