From 3ef45379c4b7f10a7f1e503cfa717c69609f6028 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 2 Dec 2017 16:11:54 +0800 Subject: [PATCH] upgrade telegram api. Signed-off-by: Bo-Yi Wu --- vendor/gopkg.in/telegram-bot-api.v4/bot.go | 280 +++++++++++++-- .../gopkg.in/telegram-bot-api.v4/configs.go | 318 +++++++++++++++++- .../gopkg.in/telegram-bot-api.v4/helpers.go | 44 +++ vendor/gopkg.in/telegram-bot-api.v4/types.go | 276 +++++++++++---- vendor/vendor.json | 6 +- 5 files changed, 803 insertions(+), 121 deletions(-) diff --git a/vendor/gopkg.in/telegram-bot-api.v4/bot.go b/vendor/gopkg.in/telegram-bot-api.v4/bot.go index 24d7777..e201944 100644 --- a/vendor/gopkg.in/telegram-bot-api.v4/bot.go +++ b/vendor/gopkg.in/telegram-bot-api.v4/bot.go @@ -7,7 +7,7 @@ import ( "encoding/json" "errors" "fmt" - "github.com/technoweenie/multipartstreamer" + "io" "io/ioutil" "log" "net/http" @@ -16,12 +16,16 @@ import ( "strconv" "strings" "time" + + "github.com/technoweenie/multipartstreamer" ) // BotAPI allows you to interact with the Telegram Bot API. type BotAPI struct { - Token string `json:"token"` - Debug bool `json:"debug"` + Token string `json:"token"` + Debug bool `json:"debug"` + Buffer int `json:"buffer"` + Self User `json:"-"` Client *http.Client `json:"-"` } @@ -41,11 +45,12 @@ func NewBotAPIWithClient(token string, client *http.Client) (*BotAPI, error) { bot := &BotAPI{ Token: token, Client: client, + Buffer: 100, } self, err := bot.GetMe() if err != nil { - return &BotAPI{}, err + return nil, err } bot.Self = self @@ -63,29 +68,47 @@ func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse, } defer resp.Body.Close() - if resp.StatusCode == http.StatusForbidden { - return APIResponse{}, errors.New(ErrAPIForbidden) - } - - 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.Println(endpoint, string(bytes)) + log.Printf("%s resp: %s", endpoint, bytes) } - var apiResp APIResponse - json.Unmarshal(bytes, &apiResp) - if !apiResp.Ok { - return APIResponse{}, errors.New(apiResp.Description) + return apiResp, errors.New(apiResp.Description) } 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 +} + // makeMessageRequest makes a request to a method that returns a Message. func (bot *BotAPI) makeMessageRequest(endpoint string, params url.Values) (Message, error) { resp, err := bot.MakeRequest(endpoint, params) @@ -183,7 +206,11 @@ func (bot *BotAPI) UploadFile(endpoint string, params map[string]string, fieldna } var apiResp APIResponse - json.Unmarshal(bytes, &apiResp) + + err = json.Unmarshal(bytes, &apiResp) + if err != nil { + return APIResponse{}, err + } if !apiResp.Ok { return APIResponse{}, errors.New(apiResp.Description) @@ -408,29 +435,29 @@ func (bot *BotAPI) RemoveWebhook() (APIResponse, error) { // If you do not have a legitimate TLS certificate, you need to include // your self signed certificate with the config. func (bot *BotAPI) SetWebhook(config WebhookConfig) (APIResponse, error) { + if config.Certificate == nil { v := url.Values{} v.Add("url", config.URL.String()) + if config.MaxConnections != 0 { + v.Add("max_connections", strconv.Itoa(config.MaxConnections)) + } return bot.MakeRequest("setWebhook", v) } params := make(map[string]string) params["url"] = config.URL.String() + if config.MaxConnections != 0 { + params["max_connections"] = strconv.Itoa(config.MaxConnections) + } resp, err := bot.UploadFile("setWebhook", params, "certificate", config.Certificate) if err != nil { return APIResponse{}, err } - var apiResp APIResponse - json.Unmarshal(resp.Result, &apiResp) - - if bot.Debug { - log.Printf("setWebhook resp: %+v\n", apiResp) - } - - return apiResp, nil + return resp, nil } // GetWebhookInfo allows you to fetch information about a webhook and if @@ -448,8 +475,8 @@ func (bot *BotAPI) GetWebhookInfo() (WebhookInfo, error) { } // GetUpdatesChan starts and returns a channel for getting updates. -func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (<-chan Update, error) { - updatesChan := make(chan Update, 100) +func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (UpdatesChannel, error) { + ch := make(chan Update, bot.Buffer) go func() { for { @@ -465,18 +492,18 @@ func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (<-chan Update, error) { for _, update := range updates { if update.UpdateID >= config.Offset { config.Offset = update.UpdateID + 1 - updatesChan <- update + ch <- update } } } }() - return updatesChan, nil + return ch, nil } // ListenForWebhook registers a http handler for a webhook. -func (bot *BotAPI) ListenForWebhook(pattern string) <-chan Update { - updatesChan := make(chan Update, 100) +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) @@ -484,10 +511,10 @@ func (bot *BotAPI) ListenForWebhook(pattern string) <-chan Update { var update Update json.Unmarshal(bytes, &update) - updatesChan <- update + ch <- update }) - return updatesChan + return ch } // AnswerInlineQuery sends a response to an inline query. @@ -535,7 +562,7 @@ func (bot *BotAPI) AnswerCallbackQuery(config CallbackConfig) (APIResponse, erro // KickChatMember kicks a user from a chat. Note that this only will work // in supergroups, and requires the bot to be an admin. Also note they // will be unable to rejoin until they are unbanned. -func (bot *BotAPI) KickChatMember(config ChatMemberConfig) (APIResponse, error) { +func (bot *BotAPI) KickChatMember(config KickChatMemberConfig) (APIResponse, error) { v := url.Values{} if config.SuperGroupUsername == "" { @@ -545,6 +572,10 @@ func (bot *BotAPI) KickChatMember(config ChatMemberConfig) (APIResponse, error) } v.Add("user_id", strconv.Itoa(config.UserID)) + if config.UntilDate != 0 { + v.Add("until_date", strconv.FormatInt(config.UntilDate, 10)) + } + bot.debugLog("kickChatMember", v, nil) return bot.MakeRequest("kickChatMember", v) @@ -662,14 +693,16 @@ func (bot *BotAPI) GetChatMember(config ChatConfigWithUser) (ChatMember, error) } // UnbanChatMember unbans a user from a chat. Note that this only will work -// in supergroups, and requires the bot to be an admin. +// in supergroups and channels, and requires the bot to be an admin. func (bot *BotAPI) UnbanChatMember(config ChatMemberConfig) (APIResponse, error) { v := url.Values{} - if config.SuperGroupUsername == "" { - v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) - } else { + if config.SuperGroupUsername != "" { v.Add("chat_id", config.SuperGroupUsername) + } else if config.ChannelUsername != "" { + v.Add("chat_id", config.ChannelUsername) + } else { + v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) } v.Add("user_id", strconv.Itoa(config.UserID)) @@ -678,6 +711,86 @@ func (bot *BotAPI) UnbanChatMember(config ChatMemberConfig) (APIResponse, error) return bot.MakeRequest("unbanChatMember", v) } +// RestrictChatMember to restrict a user in a supergroup. The bot must be an +//administrator in the supergroup for this to work and must have the +//appropriate admin rights. Pass True for all boolean parameters to lift +//restrictions from a user. Returns True on success. +func (bot *BotAPI) RestrictChatMember(config RestrictChatMemberConfig) (APIResponse, error) { + v := url.Values{} + + if config.SuperGroupUsername != "" { + v.Add("chat_id", config.SuperGroupUsername) + } else if config.ChannelUsername != "" { + v.Add("chat_id", config.ChannelUsername) + } else { + v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) + } + v.Add("user_id", strconv.Itoa(config.UserID)) + + if &config.CanSendMessages != nil { + v.Add("can_send_messages", strconv.FormatBool(*config.CanSendMessages)) + } + if &config.CanSendMediaMessages != nil { + v.Add("can_send_media_messages", strconv.FormatBool(*config.CanSendMediaMessages)) + } + if &config.CanSendOtherMessages != nil { + v.Add("can_send_other_messages", strconv.FormatBool(*config.CanSendOtherMessages)) + } + if &config.CanAddWebPagePreviews != nil { + v.Add("can_add_web_page_previews", strconv.FormatBool(*config.CanAddWebPagePreviews)) + } + if config.UntilDate != 0 { + v.Add("until_date", strconv.FormatInt(config.UntilDate, 10)) + } + + bot.debugLog("restrictChatMember", v, nil) + + return bot.MakeRequest("restrictChatMember", v) +} + +// PromoteChatMember add admin rights to user +func (bot *BotAPI) PromoteChatMember(config PromoteChatMemberConfig) (APIResponse, error) { + v := url.Values{} + + if config.SuperGroupUsername != "" { + v.Add("chat_id", config.SuperGroupUsername) + } else if config.ChannelUsername != "" { + v.Add("chat_id", config.ChannelUsername) + } else { + v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) + } + v.Add("user_id", strconv.Itoa(config.UserID)) + + if &config.CanChangeInfo != nil { + v.Add("can_change_info", strconv.FormatBool(*config.CanChangeInfo)) + } + if &config.CanPostMessages != nil { + v.Add("can_post_messages", strconv.FormatBool(*config.CanPostMessages)) + } + if &config.CanEditMessages != nil { + v.Add("can_edit_messages", strconv.FormatBool(*config.CanEditMessages)) + } + if &config.CanDeleteMessages != nil { + v.Add("can_delete_messages", strconv.FormatBool(*config.CanDeleteMessages)) + } + if &config.CanInviteUsers != nil { + v.Add("can_invite_users", strconv.FormatBool(*config.CanInviteUsers)) + } + if &config.CanRestrictMembers != nil { + v.Add("can_restrict_members", strconv.FormatBool(*config.CanRestrictMembers)) + } + if &config.CanPinMessages != nil { + v.Add("can_pin_messages", strconv.FormatBool(*config.CanPinMessages)) + } + if &config.CanPromoteMembers != nil { + v.Add("can_promote_members", strconv.FormatBool(*config.CanPromoteMembers)) + } + + bot.debugLog("promoteChatMember", v, nil) + + return bot.MakeRequest("promoteChatMember", v) +} + // GetGameHighScores allows you to get the high scores for a game. func (bot *BotAPI) GetGameHighScores(config GetGameHighScoresConfig) ([]GameHighScore, error) { v, _ := config.values() @@ -692,3 +805,96 @@ func (bot *BotAPI) GetGameHighScores(config GetGameHighScoresConfig) ([]GameHigh return highScores, err } + +// AnswerShippingQuery allows you to reply to Update with shipping_query parameter. +func (bot *BotAPI) AnswerShippingQuery(config ShippingConfig) (APIResponse, error) { + v := url.Values{} + + v.Add("shipping_query_id", config.ShippingQueryID) + v.Add("ok", strconv.FormatBool(config.OK)) + if config.OK == true { + data, err := json.Marshal(config.ShippingOptions) + if err != nil { + return APIResponse{}, err + } + v.Add("shipping_options", string(data)) + } else { + v.Add("error_message", config.ErrorMessage) + } + + bot.debugLog("answerShippingQuery", v, nil) + + return bot.MakeRequest("answerShippingQuery", v) +} + +// AnswerPreCheckoutQuery allows you to reply to Update with pre_checkout_query. +func (bot *BotAPI) AnswerPreCheckoutQuery(config PreCheckoutConfig) (APIResponse, error) { + v := url.Values{} + + v.Add("pre_checkout_query_id", config.PreCheckoutQueryID) + v.Add("ok", strconv.FormatBool(config.OK)) + if config.OK != true { + v.Add("error", config.ErrorMessage) + } + + bot.debugLog("answerPreCheckoutQuery", v, nil) + + return bot.MakeRequest("answerPreCheckoutQuery", v) +} + +// DeleteMessage deletes a message in a chat +func (bot *BotAPI) DeleteMessage(config DeleteMessageConfig) (APIResponse, error) { + v, err := config.values() + if err != nil { + return APIResponse{}, err + } + + bot.debugLog(config.method(), v, nil) + + return bot.MakeRequest(config.method(), v) +} + +// GetInviteLink get InviteLink for a chat +func (bot *BotAPI) GetInviteLink(config ChatConfig) (string, error) { + v := url.Values{} + + if config.SuperGroupUsername == "" { + v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) + } else { + v.Add("chat_id", config.SuperGroupUsername) + } + + resp, err := bot.MakeRequest("exportChatInviteLink", v) + if err != nil { + return "", err + } + + var inviteLink string + err = json.Unmarshal(resp.Result, &inviteLink) + + return inviteLink, err +} + +// PinChatMessage pin message in supergroup +func (bot *BotAPI) PinChatMessage(config PinChatMessageConfig) (APIResponse, error) { + v, err := config.values() + if err != nil { + return APIResponse{}, err + } + + bot.debugLog(config.method(), v, nil) + + return bot.MakeRequest(config.method(), v) +} + +// UnpinChatMessage unpin message in supergroup +func (bot *BotAPI) UnpinChatMessage(config UnpinChatMessageConfig) (APIResponse, error) { + v, err := config.values() + if err != nil { + return APIResponse{}, err + } + + bot.debugLog(config.method(), v, nil) + + return bot.MakeRequest(config.method(), v) +} diff --git a/vendor/gopkg.in/telegram-bot-api.v4/configs.go b/vendor/gopkg.in/telegram-bot-api.v4/configs.go index be8b4e5..c0293ce 100644 --- a/vendor/gopkg.in/telegram-bot-api.v4/configs.go +++ b/vendor/gopkg.in/telegram-bot-api.v4/configs.go @@ -198,7 +198,10 @@ type MessageConfig struct { // values returns a url.Values representation of MessageConfig. func (config MessageConfig) values() (url.Values, error) { - v, _ := config.BaseChat.values() + v, err := config.BaseChat.values() + if err != nil { + return v, err + } v.Add("text", config.Text) v.Add("disable_web_page_preview", strconv.FormatBool(config.DisableWebPagePreview)) if config.ParseMode != "" { @@ -223,7 +226,10 @@ type ForwardConfig struct { // values returns a url.Values representation of ForwardConfig. func (config ForwardConfig) values() (url.Values, error) { - v, _ := config.BaseChat.values() + v, err := config.BaseChat.values() + if err != nil { + return v, err + } v.Add("from_chat_id", strconv.FormatInt(config.FromChatID, 10)) v.Add("message_id", strconv.Itoa(config.MessageID)) return v, nil @@ -253,7 +259,10 @@ func (config PhotoConfig) params() (map[string]string, error) { // Values returns a url.Values representation of PhotoConfig. func (config PhotoConfig) values() (url.Values, error) { - v, _ := config.BaseChat.values() + v, err := config.BaseChat.values() + if err != nil { + return v, err + } v.Add(config.name(), config.FileID) if config.Caption != "" { @@ -283,7 +292,10 @@ type AudioConfig struct { // values returns a url.Values representation of AudioConfig. func (config AudioConfig) values() (url.Values, error) { - v, _ := config.BaseChat.values() + v, err := config.BaseChat.values() + if err != nil { + return v, err + } v.Add(config.name(), config.FileID) if config.Duration != 0 { @@ -337,13 +349,20 @@ func (config AudioConfig) method() string { // DocumentConfig contains information about a SendDocument request. type DocumentConfig struct { BaseFile + Caption string } // values returns a url.Values representation of DocumentConfig. func (config DocumentConfig) values() (url.Values, error) { - v, _ := config.BaseChat.values() + 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 } @@ -352,6 +371,10 @@ func (config DocumentConfig) values() (url.Values, error) { func (config DocumentConfig) params() (map[string]string, error) { params, _ := config.BaseFile.params() + if config.Caption != "" { + params["caption"] = config.Caption + } + return params, nil } @@ -372,7 +395,10 @@ type StickerConfig struct { // values returns a url.Values representation of StickerConfig. func (config StickerConfig) values() (url.Values, error) { - v, _ := config.BaseChat.values() + v, err := config.BaseChat.values() + if err != nil { + return v, err + } v.Add(config.name(), config.FileID) @@ -405,7 +431,10 @@ type VideoConfig struct { // values returns a url.Values representation of VideoConfig. func (config VideoConfig) values() (url.Values, error) { - v, _ := config.BaseChat.values() + v, err := config.BaseChat.values() + if err != nil { + return v, err + } v.Add(config.name(), config.FileID) if config.Duration != 0 { @@ -422,6 +451,10 @@ func (config VideoConfig) values() (url.Values, error) { func (config VideoConfig) params() (map[string]string, error) { params, _ := config.BaseFile.params() + if config.Caption != "" { + params["caption"] = config.Caption + } + return params, nil } @@ -435,6 +468,57 @@ func (config VideoConfig) method() string { return "sendVideo" } +// VideoNoteConfig contains information about a SendVideoNote request. +type VideoNoteConfig struct { + BaseFile + Duration int + Length int +} + +// values returns a url.Values representation of VideoNoteConfig. +func (config VideoNoteConfig) 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)) + } + + // Telegram API seems to have a bug, if no length is provided or it is 0, it will send an error response + if config.Length != 0 { + v.Add("length", strconv.Itoa(config.Length)) + } + + return v, nil +} + +// params returns a map[string]string representation of VideoNoteConfig. +func (config VideoNoteConfig) params() (map[string]string, error) { + params, _ := config.BaseFile.params() + + if config.Length != 0 { + params["length"] = strconv.Itoa(config.Length) + } + if config.Duration != 0 { + params["duration"] = strconv.Itoa(config.Duration) + } + + return params, nil +} + +// name returns the field name for the VideoNote. +func (config VideoNoteConfig) name() string { + return "video_note" +} + +// method returns Telegram API method name for sending VideoNote. +func (config VideoNoteConfig) method() string { + return "sendVideoNote" +} + // VoiceConfig contains information about a SendVoice request. type VoiceConfig struct { BaseFile @@ -444,12 +528,18 @@ type VoiceConfig struct { // values returns a url.Values representation of VoiceConfig. func (config VoiceConfig) values() (url.Values, error) { - v, _ := config.BaseChat.values() + 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) + } return v, nil } @@ -461,6 +551,9 @@ func (config VoiceConfig) params() (map[string]string, error) { if config.Duration != 0 { params["duration"] = strconv.Itoa(config.Duration) } + if config.Caption != "" { + params["caption"] = config.Caption + } return params, nil } @@ -484,7 +577,10 @@ type LocationConfig struct { // values returns a url.Values representation of LocationConfig. func (config LocationConfig) values() (url.Values, error) { - v, _ := config.BaseChat.values() + v, err := config.BaseChat.values() + if err != nil { + return v, err + } v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64)) v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64)) @@ -508,7 +604,10 @@ type VenueConfig struct { } func (config VenueConfig) values() (url.Values, error) { - v, _ := config.BaseChat.values() + v, err := config.BaseChat.values() + if err != nil { + return v, err + } v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64)) v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64)) @@ -534,7 +633,10 @@ type ContactConfig struct { } func (config ContactConfig) values() (url.Values, error) { - v, _ := config.BaseChat.values() + v, err := config.BaseChat.values() + if err != nil { + return v, err + } v.Add("phone_number", config.PhoneNumber) v.Add("first_name", config.FirstName) @@ -554,7 +656,10 @@ type GameConfig struct { } func (config GameConfig) values() (url.Values, error) { - v, _ := config.BaseChat.values() + v, err := config.BaseChat.values() + if err != nil { + return v, err + } v.Add("game_short_name", config.GameShortName) @@ -640,7 +745,10 @@ type ChatActionConfig struct { // values returns a url.Values representation of ChatActionConfig. func (config ChatActionConfig) values() (url.Values, error) { - v, _ := config.BaseChat.values() + v, err := config.BaseChat.values() + if err != nil { + return v, err + } v.Add("action", config.Action) return v, nil } @@ -659,7 +767,10 @@ type EditMessageTextConfig struct { } func (config EditMessageTextConfig) values() (url.Values, error) { - v, _ := config.BaseEdit.values() + v, err := config.BaseEdit.values() + if err != nil { + return v, err + } v.Add("text", config.Text) v.Add("parse_mode", config.ParseMode) @@ -726,8 +837,9 @@ type UpdateConfig struct { // WebhookConfig contains information about a SetWebhook request. type WebhookConfig struct { - URL *url.URL - Certificate interface{} + URL *url.URL + Certificate interface{} + MaxConnections int } // FileBytes contains information about a set of bytes to upload @@ -771,9 +883,39 @@ type CallbackConfig struct { type ChatMemberConfig struct { ChatID int64 SuperGroupUsername string + ChannelUsername string UserID int } +// KickChatMemberConfig contains extra fields to kick user +type KickChatMemberConfig struct { + ChatMemberConfig + UntilDate int64 +} + +// RestrictChatMemberConfig contains fields to restrict members of chat +type RestrictChatMemberConfig struct { + ChatMemberConfig + UntilDate int64 + CanSendMessages *bool + CanSendMediaMessages *bool + CanSendOtherMessages *bool + CanAddWebPagePreviews *bool +} + +// PromoteChatMemberConfig contains fields to promote members of chat +type PromoteChatMemberConfig struct { + ChatMemberConfig + CanChangeInfo *bool + CanPostMessages *bool + CanEditMessages *bool + CanDeleteMessages *bool + CanInviteUsers *bool + CanRestrictMembers *bool + CanPinMessages *bool + CanPromoteMembers *bool +} + // ChatConfig contains information about getting information on a chat. type ChatConfig struct { ChatID int64 @@ -787,3 +929,147 @@ type ChatConfigWithUser struct { SuperGroupUsername string UserID int } + +// InvoiceConfig contains information for sendInvoice request. +type InvoiceConfig struct { + BaseChat + Title string // required + Description string // required + Payload string // required + ProviderToken string // required + StartParameter string // required + Currency string // required + Prices *[]LabeledPrice // required + PhotoURL string + PhotoSize int + PhotoWidth int + PhotoHeight int + NeedName bool + NeedPhoneNumber bool + NeedEmail bool + NeedShippingAddress bool + IsFlexible bool +} + +func (config InvoiceConfig) values() (url.Values, error) { + v, err := config.BaseChat.values() + if err != nil { + return v, err + } + v.Add("title", config.Title) + v.Add("description", config.Description) + v.Add("payload", config.Payload) + v.Add("provider_token", config.ProviderToken) + v.Add("start_parameter", config.StartParameter) + v.Add("currency", config.Currency) + data, err := json.Marshal(config.Prices) + if err != nil { + return v, err + } + v.Add("prices", string(data)) + if config.PhotoURL != "" { + v.Add("photo_url", config.PhotoURL) + } + if config.PhotoSize != 0 { + v.Add("photo_size", strconv.Itoa(config.PhotoSize)) + } + if config.PhotoWidth != 0 { + v.Add("photo_width", strconv.Itoa(config.PhotoWidth)) + } + if config.PhotoHeight != 0 { + v.Add("photo_height", strconv.Itoa(config.PhotoHeight)) + } + if config.NeedName != false { + v.Add("need_name", strconv.FormatBool(config.NeedName)) + } + if config.NeedPhoneNumber != false { + v.Add("need_phone_number", strconv.FormatBool(config.NeedPhoneNumber)) + } + if config.NeedEmail != false { + v.Add("need_email", strconv.FormatBool(config.NeedEmail)) + } + if config.NeedShippingAddress != false { + v.Add("need_shipping_address", strconv.FormatBool(config.NeedShippingAddress)) + } + if config.IsFlexible != false { + v.Add("is_flexible", strconv.FormatBool(config.IsFlexible)) + } + + return v, nil +} + +func (config InvoiceConfig) method() string { + return "sendInvoice" +} + +// ShippingConfig contains information for answerShippingQuery request. +type ShippingConfig struct { + ShippingQueryID string // required + OK bool // required + ShippingOptions *[]ShippingOption + ErrorMessage string +} + +// PreCheckoutConfig conatins information for answerPreCheckoutQuery request. +type PreCheckoutConfig struct { + PreCheckoutQueryID string // required + OK bool // required + ErrorMessage string +} + +// DeleteMessageConfig contains information of a message in a chat to delete. +type DeleteMessageConfig struct { + ChatID int64 + MessageID int +} + +func (config DeleteMessageConfig) method() string { + return "deleteMessage" +} + +func (config DeleteMessageConfig) values() (url.Values, error) { + v := url.Values{} + + v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) + v.Add("message_id", strconv.Itoa(config.MessageID)) + + return v, nil +} + +// PinChatMessageConfig contains information of a message in a chat to pin. +type PinChatMessageConfig struct { + ChatID int64 + MessageID int + DisableNotification bool +} + +func (config PinChatMessageConfig) method() string { + return "pinChatMessage" +} + +func (config PinChatMessageConfig) values() (url.Values, error) { + v := url.Values{} + + v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) + v.Add("message_id", strconv.Itoa(config.MessageID)) + v.Add("disable_notification", strconv.FormatBool(config.DisableNotification)) + + return v, nil +} + +// UnpinChatMessageConfig contains information of chat to unpin. +type UnpinChatMessageConfig struct { + ChatID int64 +} + +func (config UnpinChatMessageConfig) method() string { + return "unpinChatMessage" +} + +func (config UnpinChatMessageConfig) values() (url.Values, error) { + v := url.Values{} + + v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) + + return v, nil +} \ No newline at end of file diff --git a/vendor/gopkg.in/telegram-bot-api.v4/helpers.go b/vendor/gopkg.in/telegram-bot-api.v4/helpers.go index 476eeeb..132d957 100644 --- a/vendor/gopkg.in/telegram-bot-api.v4/helpers.go +++ b/vendor/gopkg.in/telegram-bot-api.v4/helpers.go @@ -194,6 +194,37 @@ func NewVideoShare(chatID int64, fileID string) VideoConfig { } } +// NewVideoNoteUpload creates a new video note uploader. +// +// chatID is where to send it, file is a string path to the file, +// FileReader, or FileBytes. +func NewVideoNoteUpload(chatID int64, length int, file interface{}) VideoNoteConfig { + return VideoNoteConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + File: file, + UseExisting: false, + }, + Length: length, + } +} + +// NewVideoNoteShare shares an existing video. +// You may use this to reshare an existing video without reuploading it. +// +// chatID is where to send it, fileID is the ID of the video +// already uploaded. +func NewVideoNoteShare(chatID int64, length int, fileID string) VideoNoteConfig { + return VideoNoteConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + FileID: fileID, + UseExisting: true, + }, + Length: length, + } +} + // NewVoiceUpload creates a new voice uploader. // // chatID is where to send it, file is a string path to the file, @@ -609,3 +640,16 @@ func NewCallbackWithAlert(id, text string) CallbackConfig { ShowAlert: true, } } + +// NewInvoice created 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}, + Title: title, + Description: description, + Payload: payload, + ProviderToken: providerToken, + StartParameter: startParameter, + Currency: currency, + Prices: prices} +} diff --git a/vendor/gopkg.in/telegram-bot-api.v4/types.go b/vendor/gopkg.in/telegram-bot-api.v4/types.go index b43f4a0..bef68b8 100644 --- a/vendor/gopkg.in/telegram-bot-api.v4/types.go +++ b/vendor/gopkg.in/telegram-bot-api.v4/types.go @@ -35,14 +35,28 @@ type Update struct { InlineQuery *InlineQuery `json:"inline_query"` ChosenInlineResult *ChosenInlineResult `json:"chosen_inline_result"` CallbackQuery *CallbackQuery `json:"callback_query"` + ShippingQuery *ShippingQuery `json:"shipping_query"` + PreCheckoutQuery *PreCheckoutQuery `json:"pre_checkout_query"` +} + +// UpdatesChannel is the channel for getting updates. +type UpdatesChannel <-chan Update + +// Clear discards all unprocessed incoming updates. +func (ch UpdatesChannel) Clear() { + for len(ch) != 0 { + <-ch + } } // User is a user on Telegram. type User struct { - ID int `json:"id"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` // optional - UserName string `json:"username"` // optional + ID int `json:"id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` // optional + UserName string `json:"username"` // optional + LanguageCode string `json:"language_code"` // optional + IsBot bool `json:"is_bot"` // optional } // String displays a simple text version of a user. @@ -68,15 +82,24 @@ type GroupChat struct { Title string `json:"title"` } +// ChatPhoto represents a chat photo. +type ChatPhoto struct { + SmallFileID string `json:"small_file_id"` + BigFileID string `json:"big_file_id"` +} + // Chat contains information about the place a message was sent. type Chat struct { - ID int64 `json:"id"` - Type string `json:"type"` - Title string `json:"title"` // optional - UserName string `json:"username"` // optional - FirstName string `json:"first_name"` // optional - LastName string `json:"last_name"` // optional - AllMembersAreAdmins bool `json:"all_members_are_administrators"` // optional + ID int64 `json:"id"` + Type string `json:"type"` + Title string `json:"title"` // optional + UserName string `json:"username"` // optional + FirstName string `json:"first_name"` // optional + LastName string `json:"last_name"` // optional + AllMembersAreAdmins bool `json:"all_members_are_administrators"` // optional + Photo *ChatPhoto `json:"photo"` + Description string `json:"description,omitempty"` // optional + InviteLink string `json:"invite_link,omitempty"` // optional } // IsPrivate returns if the Chat is a private conversation. @@ -107,40 +130,43 @@ func (c Chat) ChatConfig() ChatConfig { // Message is returned by almost every request, and contains data about // almost anything. type Message struct { - MessageID int `json:"message_id"` - From *User `json:"from"` // optional - Date int `json:"date"` - Chat *Chat `json:"chat"` - ForwardFrom *User `json:"forward_from"` // optional - ForwardFromChat *Chat `json:"forward_from_chat"` // optional - ForwardFromMessageID int `json:"forward_from_message_id"` // optional - ForwardDate int `json:"forward_date"` // optional - ReplyToMessage *Message `json:"reply_to_message"` // optional - EditDate int `json:"edit_date"` // optional - Text string `json:"text"` // optional - Entities *[]MessageEntity `json:"entities"` // optional - Audio *Audio `json:"audio"` // optional - Document *Document `json:"document"` // optional - Game *Game `json:"game"` // optional - Photo *[]PhotoSize `json:"photo"` // optional - Sticker *Sticker `json:"sticker"` // optional - Video *Video `json:"video"` // optional - Voice *Voice `json:"voice"` // optional - Caption string `json:"caption"` // optional - Contact *Contact `json:"contact"` // optional - Location *Location `json:"location"` // optional - Venue *Venue `json:"venue"` // optional - NewChatMember *User `json:"new_chat_member"` // optional - LeftChatMember *User `json:"left_chat_member"` // optional - NewChatTitle string `json:"new_chat_title"` // optional - NewChatPhoto *[]PhotoSize `json:"new_chat_photo"` // optional - DeleteChatPhoto bool `json:"delete_chat_photo"` // optional - GroupChatCreated bool `json:"group_chat_created"` // optional - SuperGroupChatCreated bool `json:"supergroup_chat_created"` // optional - ChannelChatCreated bool `json:"channel_chat_created"` // optional - MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional - MigrateFromChatID int64 `json:"migrate_from_chat_id"` // optional - PinnedMessage *Message `json:"pinned_message"` // optional + MessageID int `json:"message_id"` + From *User `json:"from"` // optional + Date int `json:"date"` + Chat *Chat `json:"chat"` + ForwardFrom *User `json:"forward_from"` // optional + ForwardFromChat *Chat `json:"forward_from_chat"` // optional + ForwardFromMessageID int `json:"forward_from_message_id"` // optional + ForwardDate int `json:"forward_date"` // optional + ReplyToMessage *Message `json:"reply_to_message"` // optional + EditDate int `json:"edit_date"` // optional + Text string `json:"text"` // optional + Entities *[]MessageEntity `json:"entities"` // optional + Audio *Audio `json:"audio"` // optional + Document *Document `json:"document"` // optional + Game *Game `json:"game"` // optional + Photo *[]PhotoSize `json:"photo"` // optional + Sticker *Sticker `json:"sticker"` // optional + Video *Video `json:"video"` // optional + VideoNote *VideoNote `json:"video_note"` // optional + Voice *Voice `json:"voice"` // optional + Caption string `json:"caption"` // optional + Contact *Contact `json:"contact"` // optional + Location *Location `json:"location"` // optional + Venue *Venue `json:"venue"` // optional + NewChatMembers *[]User `json:"new_chat_members"` // optional + LeftChatMember *User `json:"left_chat_member"` // optional + NewChatTitle string `json:"new_chat_title"` // optional + NewChatPhoto *[]PhotoSize `json:"new_chat_photo"` // optional + DeleteChatPhoto bool `json:"delete_chat_photo"` // optional + GroupChatCreated bool `json:"group_chat_created"` // optional + SuperGroupChatCreated bool `json:"supergroup_chat_created"` // optional + ChannelChatCreated bool `json:"channel_chat_created"` // optional + MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional + MigrateFromChatID int64 `json:"migrate_from_chat_id"` // optional + PinnedMessage *Message `json:"pinned_message"` // optional + Invoice *Invoice `json:"invoice"` // optional + SuccessfulPayment *SuccessfulPayment `json:"successful_payment"` // optional } // Time converts the message timestamp into a Time. @@ -148,21 +174,23 @@ func (m *Message) Time() time.Time { return time.Unix(int64(m.Date), 0) } -// IsCommand returns true if message starts with '/'. +// IsCommand returns true if message starts with a "bot_command" entity. func (m *Message) IsCommand() bool { - return m.Text != "" && m.Text[0] == '/' + if m.Entities == nil || len(*m.Entities) == 0 { + return false + } + + entity := (*m.Entities)[0] + return entity.Offset == 0 && entity.Type == "bot_command" } // Command checks if the message was a command and if it was, returns the // command. If the Message was not a command, it returns an empty string. // -// If the command contains the at bot syntax, it removes the bot name. +// If the command contains the at name syntax, it is removed. Use +// CommandWithAt() if you do not want that. func (m *Message) Command() string { - if !m.IsCommand() { - return "" - } - - command := strings.SplitN(m.Text, " ", 2)[0][1:] + command := m.CommandWithAt() if i := strings.Index(command, "@"); i != -1 { command = command[:i] @@ -171,20 +199,42 @@ func (m *Message) Command() string { return command } +// CommandWithAt checks if the message was a command and if it was, returns the +// command. If the Message was not a command, it returns an empty string. +// +// If the command contains the at name syntax, it is not removed. Use Command() +// if you want that. +func (m *Message) CommandWithAt() string { + if !m.IsCommand() { + return "" + } + + // IsCommand() checks that the message begins with a bot_command entity + entity := (*m.Entities)[0] + return m.Text[1:entity.Length] +} + // CommandArguments checks if the message was a command and if it was, // returns all text after the command name. If the Message was not a // command, it returns an empty string. +// +// Note: The first character after the command name is omitted: +// - "/foo bar baz" yields "bar baz", not " bar baz" +// - "/foo-bar baz" yields "bar baz", too +// Even though the latter is not a command conforming to the spec, the API +// marks "/foo" as command entity. func (m *Message) CommandArguments() string { if !m.IsCommand() { return "" } - split := strings.SplitN(m.Text, " ", 2) - if len(split) != 2 { - return "" + // IsCommand() checks that the message begins with a bot_command entity + entity := (*m.Entities)[0] + if len(m.Text) == entity.Length { + return "" // The command makes up the whole message + } else { + return m.Text[entity.Length+1:] } - - return strings.SplitN(m.Text, " ", 2)[1] } // MessageEntity contains information about data in a Message. @@ -253,6 +303,15 @@ type Video struct { FileSize int `json:"file_size"` // optional } +// VideoNote contains information about a video. +type VideoNote struct { + FileID string `json:"file_id"` + Length int `json:"length"` + Duration int `json:"duration"` + Thumbnail *PhotoSize `json:"thumb"` // optional + FileSize int `json:"file_size"` // optional +} + // Voice contains information about a voice. type Voice struct { FileID string `json:"file_id"` @@ -346,11 +405,12 @@ type InlineKeyboardMarkup struct { // CallbackGame, if set, MUST be first button in first row. type InlineKeyboardButton struct { Text string `json:"text"` - URL *string `json:"url,omitempty"` // optional - CallbackData *string `json:"callback_data,omitempty"` // optional - SwitchInlineQuery *string `json:"switch_inline_query,omitempty"` // optional - SwitchInlineQueryCurrentChat *string `json:"switch_inline_query_current_chat"` // optional - CallbackGame *CallbackGame `json:"callback_game"` // optional + URL *string `json:"url,omitempty"` // optional + CallbackData *string `json:"callback_data,omitempty"` // optional + SwitchInlineQuery *string `json:"switch_inline_query,omitempty"` // optional + SwitchInlineQueryCurrentChat *string `json:"switch_inline_query_current_chat,omitempty"` // optional + CallbackGame *CallbackGame `json:"callback_game,omitempty"` // optional + Pay bool `json:"pay,omitempty"` // optional } // CallbackQuery is data sent when a keyboard button with callback data @@ -374,8 +434,22 @@ type ForceReply struct { // ChatMember is information about a member in a chat. type ChatMember struct { - User *User `json:"user"` - Status string `json:"status"` + User *User `json:"user"` + Status string `json:"status"` + UntilDate int64 `json:"until_date,omitempty"` // optional + CanBeEdited bool `json:"can_be_edited,omitempty"` // optional + CanChangeInfo bool `json:"can_change_info,omitempty"` // optional + CanPostMessages bool `json:"can_post_messages,omitempty"` // optional + CanEditMessages bool `json:"can_edit_messages,omitempty"` // optional + CanDeleteMessages bool `json:"can_delete_messages,omitempty"` // optional + CanInviteUsers bool `json:"can_invite_users,omitempty"` // optional + CanRestrictMembers bool `json:"can_restrict_members,omitempty"` // optional + CanPinMessages bool `json:"can_pin_messages,omitempty"` // optional + CanPromoteMembers bool `json:"can_promote_members,omitempty"` // optional + CanSendMessages bool `json:"can_send_messages,omitempty"` // optional + CanSendMediaMessages bool `json:"can_send_media_messages,omitempty"` // optional + CanSendOtherMessages bool `json:"can_send_other_messages,omitempty"` // optional + CanAddWebPagePreviews bool `json:"can_add_web_page_previews,omitempty"` // optional } // IsCreator returns if the ChatMember was the creator of the chat. @@ -483,6 +557,7 @@ type InlineQueryResultGIF struct { 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"` Title string `json:"title"` Caption string `json:"caption"` @@ -497,6 +572,7 @@ type InlineQueryResultMPEG4GIF struct { URL string `json:"mpeg4_url"` // required Width int `json:"mpeg4_width"` Height int `json:"mpeg4_height"` + Duration int `json:"mpeg4_duration"` ThumbURL string `json:"thumb_url"` Title string `json:"title"` Caption string `json:"caption"` @@ -625,3 +701,73 @@ type InputContactMessageContent struct { FirstName string `json:"first_name"` LastName string `json:"last_name"` } + +// Invoice contains basic information about an invoice. +type Invoice struct { + Title string `json:"title"` + Description string `json:"description"` + StartParameter string `json:"start_parameter"` + Currency string `json:"currency"` + TotalAmount int `json:"total_amount"` +} + +// LabeledPrice represents a portion of the price for goods or services. +type LabeledPrice struct { + Label string `json:"label"` + Amount int `json:"amount"` +} + +// ShippingAddress represents a shipping address. +type ShippingAddress struct { + CountryCode string `json:"country_code"` + State string `json:"state"` + City string `json:"city"` + StreetLine1 string `json:"street_line1"` + StreetLine2 string `json:"street_line2"` + PostCode string `json:"post_code"` +} + +// OrderInfo represents information about an order. +type OrderInfo struct { + Name string `json:"name,omitempty"` + PhoneNumber string `json:"phone_number,omitempty"` + Email string `json:"email,omitempty"` + ShippingAddress *ShippingAddress `json:"shipping_address,omitempty"` +} + +// ShippingOption represents one shipping option. +type ShippingOption struct { + ID string `json:"id"` + Title string `json:"title"` + Prices *[]LabeledPrice `json:"prices"` +} + +// SuccessfulPayment contains basic information about a successful payment. +type SuccessfulPayment struct { + Currency string `json:"currency"` + TotalAmount int `json:"total_amount"` + InvoicePayload string `json:"invoice_payload"` + ShippingOptionID string `json:"shipping_option_id,omitempty"` + OrderInfo *OrderInfo `json:"order_info,omitempty"` + TelegramPaymentChargeID string `json:"telegram_payment_charge_id"` + ProviderPaymentChargeID string `json:"provider_payment_charge_id"` +} + +// ShippingQuery contains information about an incoming shipping query. +type ShippingQuery struct { + ID string `json:"id"` + From *User `json:"from"` + InvoicePayload string `json:"invoice_payload"` + ShippingAddress *ShippingAddress `json:"shipping_address"` +} + +// PreCheckoutQuery contains information about an incoming pre-checkout query. +type PreCheckoutQuery struct { + ID string `json:"id"` + From *User `json:"from"` + Currency string `json:"currency"` + TotalAmount int `json:"total_amount"` + InvoicePayload string `json:"invoice_payload"` + ShippingOptionID string `json:"shipping_option_id,omitempty"` + OrderInfo *OrderInfo `json:"order_info,omitempty"` +} diff --git a/vendor/vendor.json b/vendor/vendor.json index a4434d6..67a0606 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -75,10 +75,10 @@ "revisionTime": "2017-06-25T03:28:08Z" }, { - "checksumSHA1": "AzeP9gpBdHRcPQ5ozBpkBcqfInk=", + "checksumSHA1": "JaRjqSc0lOpA0V7avHWBex1IY+w=", "path": "gopkg.in/telegram-bot-api.v4", - "revision": "0a57807db79efce7f6719fbb2c0e0f83fda79aec", - "revisionTime": "2016-11-25T05:50:35Z" + "revision": "2022d04b94f50056a09962b1ac81cdd821d20a55", + "revisionTime": "2017-11-09T18:51:50Z" } ], "rootPath": "github.com/appleboy/drone-telegram"