Resolve develop and master conflicts
@@ -62,60 +62,7 @@ }
} ``` -There are more examples on the [wiki](https://github.com/go-telegram-bot-api/telegram-bot-api/wiki) +There are more examples on the [site](https://go-telegram-bot-api.github.io/) with detailed information on how to do many different kinds of things. It's a great place to get started on using keyboards, commands, or other kinds of reply markup. - -If you need to use webhooks (if you wish to run on Google App Engine), -you may use a slightly different method. - -```go -package main - -import ( - "log" - "net/http" - - "github.com/go-telegram-bot-api/telegram-bot-api" -) - -func main() { - bot, err := tgbotapi.NewBotAPI("MyAwesomeBotToken") - if err != nil { - log.Fatal(err) - } - - bot.Debug = true - - log.Printf("Authorized on account %s", bot.Self.UserName) - - _, err = bot.SetWebhook(tgbotapi.NewWebhookWithCert("https://www.google.com:8443/"+bot.Token, "cert.pem")) - if err != nil { - log.Fatal(err) - } - info, err := bot.GetWebhookInfo() - if err != nil { - log.Fatal(err) - } - if info.LastErrorDate != 0 { - log.Printf("Telegram callback failed: %s", info.LastErrorMessage) - } - updates := bot.ListenForWebhook("/" + bot.Token) - go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil) - - for update := range updates { - log.Printf("%+v\n", update) - } -} -``` - -If you need, you may generate a self signed certficate, as this requires -HTTPS / TLS. The above example tells Telegram that this is your -certificate and that it should be trusted, even though it is not -properly signed. - - openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 3560 -subj "//O=Org\CN=Test" -nodes - -Now that [Let's Encrypt](https://letsencrypt.org) is available, -you may wish to generate your free TLS certificate there.
@@ -12,7 +12,6 @@ "io/ioutil"
"net/http" "net/url" "os" - "strconv" "strings" "time"@@ -67,11 +66,31 @@ func (b *BotAPI) SetAPIEndpoint(apiEndpoint string) {
b.apiEndpoint = apiEndpoint } +func buildParams(in Params) (out url.Values) { + if in == nil { + return url.Values{} + } + + out = url.Values{} + + for key, value := range in { + out.Set(key, value) + } + + return +} + // MakeRequest makes a request to a specific endpoint with our token. -func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse, error) { +func (bot *BotAPI) MakeRequest(endpoint string, params Params) (APIResponse, error) { + if bot.Debug { + log.Printf("Endpoint: %s, params: %v\n", endpoint, params) + } + method := fmt.Sprintf(bot.apiEndpoint, bot.Token, endpoint) - resp, err := bot.Client.PostForm(method, params) + values := buildParams(params) + + resp, err := bot.Client.PostForm(method, values) if err != nil { return APIResponse{}, err }@@ -84,15 +103,21 @@ return apiResp, err
} if bot.Debug { - log.Printf("%s resp: %s", endpoint, bytes) + log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes)) } if !apiResp.Ok { - parameters := ResponseParameters{} + var parameters ResponseParameters + if apiResp.Parameters != nil { parameters = *apiResp.Parameters } - return apiResp, Error{Code: apiResp.ErrorCode, Message: apiResp.Description, ResponseParameters: parameters} + + return apiResp, Error{ + Code: apiResp.ErrorCode, + Message: apiResp.Description, + ResponseParameters: parameters, + } } return apiResp, nil@@ -122,21 +147,6 @@
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) - if err != nil { - return Message{}, err - } - - var message Message - json.Unmarshal(resp.Result, &message) - - bot.debugLog(endpoint, params, message) - - return message, nil -} - // UploadFile makes a request to the API with a file. // // Requires the parameter to hold the file not be in the params.@@ -145,7 +155,7 @@ // a FileReader struct, or a url.URL.
// // Note that if your FileReader has a size set to -1, it will read // the file into memory to calculate a size. -func (bot *BotAPI) UploadFile(endpoint string, params map[string]string, fieldname string, file interface{}) (APIResponse, error) { +func (bot *BotAPI) UploadFile(endpoint string, params Params, fieldname string, file interface{}) (APIResponse, error) { ms := multipartstreamer.New() switch f := file.(type) {@@ -194,6 +204,10 @@ default:
return APIResponse{}, errors.New(ErrBadFileType) } + if bot.Debug { + log.Printf("Endpoint: %s, fieldname: %s, params: %v, file: %T\n", endpoint, fieldname, params, file) + } + method := fmt.Sprintf(bot.apiEndpoint, bot.Token, endpoint) req, err := http.NewRequest("POST", method, nil)@@ -215,7 +229,7 @@ return APIResponse{}, err
} if bot.Debug { - log.Println(string(bytes)) + log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes)) } var apiResp APIResponse@@ -257,11 +271,9 @@ return User{}, err
} var user User - json.Unmarshal(resp.Result, &user) - - bot.debugLog("getMe", nil, user) + err = json.Unmarshal(resp.Result, &user) - return user, nil + return user, err } // IsMessageToMe returns true if message directed to this bot.@@ -271,90 +283,52 @@ func (bot *BotAPI) IsMessageToMe(message Message) bool {
return strings.Contains(message.Text, "@"+bot.Self.UserName) } -// Send will send a Chattable item to Telegram. -// -// It requires the Chattable to send. -func (bot *BotAPI) Send(c Chattable) (Message, error) { - switch c.(type) { - case Fileable: - return bot.sendFile(c.(Fileable)) - default: - return bot.sendChattable(c) +// Request sends a Chattable to Telegram, and returns the APIResponse. +func (bot *BotAPI) Request(c Chattable) (APIResponse, error) { + params, err := c.params() + if err != nil { + return APIResponse{}, err } -} -// debugLog checks if the bot is currently running in debug mode, and if -// so will display information about the request and response in the -// debug log. -func (bot *BotAPI) debugLog(context string, v url.Values, message interface{}) { - if bot.Debug { - log.Printf("%s req : %+v\n", context, v) - log.Printf("%s resp: %+v\n", context, message) - } -} + switch t := c.(type) { + case Fileable: + if t.useExistingFile() { + return bot.MakeRequest(t.method(), params) + } -// sendExisting will send a Message with an existing file to Telegram. -func (bot *BotAPI) sendExisting(method string, config Fileable) (Message, error) { - v, err := config.values() - - if err != nil { - return Message{}, err + return bot.UploadFile(t.method(), params, t.name(), t.getFile()) + default: + return bot.MakeRequest(c.method(), params) } - - message, err := bot.makeMessageRequest(method, v) - if err != nil { - return Message{}, err - } - - return message, nil } -// uploadAndSend will send a Message with a new file to Telegram. -func (bot *BotAPI) uploadAndSend(method string, config Fileable) (Message, error) { - params, err := config.params() - if err != nil { - return Message{}, err - } - - file := config.getFile() - - resp, err := bot.UploadFile(method, params, config.name(), file) +// Send will send a Chattable item to Telegram and provides the +// returned Message. +func (bot *BotAPI) Send(c Chattable) (Message, error) { + resp, err := bot.Request(c) if err != nil { return Message{}, err } var message Message - json.Unmarshal(resp.Result, &message) + err = json.Unmarshal(resp.Result, &message) - bot.debugLog(method, nil, message) - - return message, nil + return message, err } -// sendFile determines if the file is using an existing file or uploading -// a new file, then sends it as needed. -func (bot *BotAPI) sendFile(config Fileable) (Message, error) { - if config.useExistingFile() { - return bot.sendExisting(config.method(), config) - } - - return bot.uploadAndSend(config.method(), config) -} +// SendMediaGroup sends a media group and returns the resulting messages. +func (bot *BotAPI) SendMediaGroup(config MediaGroupConfig) ([]Message, error) { + params, _ := config.params() -// sendChattable sends a Chattable. -func (bot *BotAPI) sendChattable(config Chattable) (Message, error) { - v, err := config.values() + resp, err := bot.MakeRequest(config.method(), params) if err != nil { - return Message{}, err + return nil, err } - message, err := bot.makeMessageRequest(config.method(), v) + var messages []Message + err = json.Unmarshal(resp.Result, &messages) - if err != nil { - return Message{}, err - } - - return message, nil + return messages, err } // GetUserProfilePhotos gets a user's profile photos.@@ -362,46 +336,34 @@ //
// It requires UserID. // Offset and Limit are optional. func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) { - v := url.Values{} - v.Add("user_id", strconv.Itoa(config.UserID)) - if config.Offset != 0 { - v.Add("offset", strconv.Itoa(config.Offset)) - } - if config.Limit != 0 { - v.Add("limit", strconv.Itoa(config.Limit)) - } + params, _ := config.params() - resp, err := bot.MakeRequest("getUserProfilePhotos", v) + resp, err := bot.MakeRequest(config.method(), params) if err != nil { return UserProfilePhotos{}, err } var profilePhotos UserProfilePhotos - json.Unmarshal(resp.Result, &profilePhotos) + err = json.Unmarshal(resp.Result, &profilePhotos) - bot.debugLog("GetUserProfilePhoto", v, profilePhotos) - - return profilePhotos, nil + return profilePhotos, err } // GetFile returns a File which can download a file from Telegram. // // Requires FileID. func (bot *BotAPI) GetFile(config FileConfig) (File, error) { - v := url.Values{} - v.Add("file_id", config.FileID) + params, _ := config.params() - resp, err := bot.MakeRequest("getFile", v) + resp, err := bot.MakeRequest(config.method(), params) if err != nil { return File{}, err } var file File - json.Unmarshal(resp.Result, &file) + err = json.Unmarshal(resp.Result, &file) - bot.debugLog("GetFile", v, file) - - return file, nil + return file, err } // GetUpdates fetches updates.@@ -412,71 +374,23 @@ // To avoid stale items, set Offset to one higher than the previous item.
// Set Timeout to a large number to reduce requests so you can get updates // instantly instead of having to wait between requests. func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) { - v := url.Values{} - if config.Offset != 0 { - v.Add("offset", strconv.Itoa(config.Offset)) - } - if config.Limit > 0 { - v.Add("limit", strconv.Itoa(config.Limit)) - } - if config.Timeout > 0 { - v.Add("timeout", strconv.Itoa(config.Timeout)) - } + params, _ := config.params() - resp, err := bot.MakeRequest("getUpdates", v) + resp, err := bot.MakeRequest(config.method(), params) if err != nil { return []Update{}, err } var updates []Update - json.Unmarshal(resp.Result, &updates) - - bot.debugLog("getUpdates", v, updates) - - return updates, nil -} - -// RemoveWebhook unsets the webhook. -func (bot *BotAPI) RemoveWebhook() (APIResponse, error) { - return bot.MakeRequest("setWebhook", url.Values{}) -} - -// SetWebhook sets a webhook. -// -// If this is set, GetUpdates will not get any data! -// -// 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 - } + err = json.Unmarshal(resp.Result, &updates) - return resp, nil + return updates, err } // GetWebhookInfo allows you to fetch information about a webhook and if // one currently is set, along with pending update count and error messages. func (bot *BotAPI) GetWebhookInfo() (WebhookInfo, error) { - resp, err := bot.MakeRequest("getWebhookInfo", url.Values{}) + resp, err := bot.MakeRequest("getWebhookInfo", nil) if err != nil { return WebhookInfo{}, err }@@ -488,7 +402,7 @@ return info, err
} // GetUpdatesChan starts and returns a channel for getting updates. -func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (UpdatesChannel, error) { +func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) UpdatesChannel { ch := make(chan Update, bot.Buffer) go func() {@@ -517,7 +431,7 @@ }
} }() - return ch, nil + return ch } // StopReceivingUpdates stops the go routine which receives updates@@ -545,96 +459,11 @@
return ch } -// AnswerInlineQuery sends a response to an inline query. -// -// Note that you must respond to an inline query within 30 seconds. -func (bot *BotAPI) AnswerInlineQuery(config InlineConfig) (APIResponse, error) { - v := url.Values{} - - v.Add("inline_query_id", config.InlineQueryID) - v.Add("cache_time", strconv.Itoa(config.CacheTime)) - v.Add("is_personal", strconv.FormatBool(config.IsPersonal)) - v.Add("next_offset", config.NextOffset) - data, err := json.Marshal(config.Results) - if err != nil { - return APIResponse{}, err - } - v.Add("results", string(data)) - v.Add("switch_pm_text", config.SwitchPMText) - v.Add("switch_pm_parameter", config.SwitchPMParameter) - - bot.debugLog("answerInlineQuery", v, nil) - - return bot.MakeRequest("answerInlineQuery", v) -} - -// AnswerCallbackQuery sends a response to an inline query callback. -func (bot *BotAPI) AnswerCallbackQuery(config CallbackConfig) (APIResponse, error) { - v := url.Values{} - - v.Add("callback_query_id", config.CallbackQueryID) - if config.Text != "" { - v.Add("text", config.Text) - } - v.Add("show_alert", strconv.FormatBool(config.ShowAlert)) - if config.URL != "" { - v.Add("url", config.URL) - } - v.Add("cache_time", strconv.Itoa(config.CacheTime)) - - bot.debugLog("answerCallbackQuery", v, nil) - - return bot.MakeRequest("answerCallbackQuery", v) -} - -// 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 KickChatMemberConfig) (APIResponse, error) { - v := url.Values{} - - if config.SuperGroupUsername == "" { - v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) - } else { - v.Add("chat_id", config.SuperGroupUsername) - } - 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) -} - -// LeaveChat makes the bot leave the chat. -func (bot *BotAPI) LeaveChat(config ChatConfig) (APIResponse, error) { - v := url.Values{} - - if config.SuperGroupUsername == "" { - v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) - } else { - v.Add("chat_id", config.SuperGroupUsername) - } - - bot.debugLog("leaveChat", v, nil) - - return bot.MakeRequest("leaveChat", v) -} - // GetChat gets information about a chat. -func (bot *BotAPI) GetChat(config ChatConfig) (Chat, error) { - v := url.Values{} +func (bot *BotAPI) GetChat(config ChatInfoConfig) (Chat, error) { + params, _ := config.params() - if config.SuperGroupUsername == "" { - v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) - } else { - v.Add("chat_id", config.SuperGroupUsername) - } - - resp, err := bot.MakeRequest("getChat", v) + resp, err := bot.MakeRequest(config.method(), params) if err != nil { return Chat{}, err }@@ -642,8 +471,6 @@
var chat Chat err = json.Unmarshal(resp.Result, &chat) - bot.debugLog("getChat", v, chat) - return chat, err }@@ -651,16 +478,10 @@ // GetChatAdministrators gets a list of administrators in the chat.
// // If none have been appointed, only the creator will be returned. // Bots are not shown, even if they are an administrator. -func (bot *BotAPI) GetChatAdministrators(config ChatConfig) ([]ChatMember, error) { - v := url.Values{} - - if config.SuperGroupUsername == "" { - v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) - } else { - v.Add("chat_id", config.SuperGroupUsername) - } +func (bot *BotAPI) GetChatAdministrators(config ChatAdministratorsConfig) ([]ChatMember, error) { + params, _ := config.params() - resp, err := bot.MakeRequest("getChatAdministrators", v) + resp, err := bot.MakeRequest(config.method(), params) if err != nil { return []ChatMember{}, err }@@ -668,22 +489,14 @@
var members []ChatMember err = json.Unmarshal(resp.Result, &members) - bot.debugLog("getChatAdministrators", v, members) - return members, err } // GetChatMembersCount gets the number of users in a chat. -func (bot *BotAPI) GetChatMembersCount(config ChatConfig) (int, error) { - v := url.Values{} - - if config.SuperGroupUsername == "" { - v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) - } else { - v.Add("chat_id", config.SuperGroupUsername) - } +func (bot *BotAPI) GetChatMembersCount(config ChatMemberCountConfig) (int, error) { + params, _ := config.params() - resp, err := bot.MakeRequest("getChatMembersCount", v) + resp, err := bot.MakeRequest(config.method(), params) if err != nil { return -1, err }@@ -691,23 +504,14 @@
var count int err = json.Unmarshal(resp.Result, &count) - bot.debugLog("getChatMembersCount", v, count) - return count, err } // GetChatMember gets a specific chat member. -func (bot *BotAPI) GetChatMember(config ChatConfigWithUser) (ChatMember, error) { - v := url.Values{} +func (bot *BotAPI) GetChatMember(config GetChatMemberConfig) (ChatMember, error) { + params, _ := config.params() - if config.SuperGroupUsername == "" { - v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) - } else { - v.Add("chat_id", config.SuperGroupUsername) - } - v.Add("user_id", strconv.Itoa(config.UserID)) - - resp, err := bot.MakeRequest("getChatMember", v) + resp, err := bot.MakeRequest(config.method(), params) if err != nil { return ChatMember{}, err }@@ -715,115 +519,14 @@
var member ChatMember err = json.Unmarshal(resp.Result, &member) - bot.debugLog("getChatMember", v, member) - return member, err } -// UnbanChatMember unbans a user from a chat. Note that this only will work -// 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", 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)) - - bot.debugLog("unbanChatMember", v, nil) - - 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() + params, _ := config.params() - resp, err := bot.MakeRequest(config.method(), v) + resp, err := bot.MakeRequest(config.method(), params) if err != nil { return []GameHighScore{}, err }@@ -834,65 +537,11 @@
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) - } +func (bot *BotAPI) GetInviteLink(config ChatInviteLinkConfig) (string, error) { + params, _ := config.params() - resp, err := bot.MakeRequest("exportChatInviteLink", v) + resp, err := bot.MakeRequest(config.method(), params) if err != nil { return "", err }@@ -903,74 +552,35 @@
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) +// GetStickerSet returns a StickerSet. +func (bot *BotAPI) GetStickerSet(config GetStickerSetConfig) (StickerSet, error) { + params, _ := config.params() - return bot.MakeRequest(config.method(), v) -} - -// UnpinChatMessage unpin message in supergroup -func (bot *BotAPI) UnpinChatMessage(config UnpinChatMessageConfig) (APIResponse, error) { - v, err := config.values() + resp, err := bot.MakeRequest(config.method(), params) if err != nil { - return APIResponse{}, err + return StickerSet{}, err } - bot.debugLog(config.method(), v, nil) + var stickers StickerSet + err = json.Unmarshal(resp.Result, &stickers) - return bot.MakeRequest(config.method(), v) + 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 - } - - bot.debugLog(config.method(), v, nil) - - 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 - } - - bot.debugLog(config.method(), v, nil) - - return bot.MakeRequest(config.method(), v) -} - -// SetChatPhoto change photo of chat. -func (bot *BotAPI) SetChatPhoto(config SetChatPhotoConfig) (APIResponse, error) { +// StopPoll stops a poll and returns the result. +func (bot *BotAPI) StopPoll(config StopPollConfig) (Poll, error) { params, err := config.params() if err != nil { - return APIResponse{}, err + return Poll{}, 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() + resp, err := bot.MakeRequest(config.method(), params) if err != nil { - return APIResponse{}, err + return Poll{}, err } - bot.debugLog(config.method(), v, nil) + var poll Poll + err = json.Unmarshal(resp.Result, &poll) - return bot.MakeRequest(config.method(), v) + return poll, err }
@@ -1,14 +1,11 @@
-package tgbotapi_test +package tgbotapi import ( "io/ioutil" - "log" "net/http" "os" "testing" "time" - - "github.com/go-telegram-bot-api/telegram-bot-api" ) const (@@ -25,8 +22,8 @@ ExistingVideoNoteFileID = "DQADAgADdQAD70cQSUK41dLsRMqfAg"
ExistingStickerFileID = "BQADAgADcwADjMcoCbdl-6eB--YPAg" ) -func getBot(t *testing.T) (*tgbotapi.BotAPI, error) { - bot, err := tgbotapi.NewBotAPI(TestToken) +func getBot(t *testing.T) (*BotAPI, error) { + bot, err := NewBotAPI(TestToken) bot.Debug = true if err != nil {@@ -38,7 +35,7 @@ return bot, err
} func TestNewBotAPI_notoken(t *testing.T) { - _, err := tgbotapi.NewBotAPI("") + _, err := NewBotAPI("") if err == nil { t.Error(err)@@ -49,7 +46,7 @@
func TestGetUpdates(t *testing.T) { bot, _ := getBot(t) - u := tgbotapi.NewUpdate(0) + u := NewUpdate(0) _, err := bot.GetUpdates(u)@@ -62,7 +59,7 @@
func TestSendWithMessage(t *testing.T) { bot, _ := getBot(t) - msg := tgbotapi.NewMessage(ChatID, "A test message from the test library in telegram-bot-api") + msg := NewMessage(ChatID, "A test message from the test library in telegram-bot-api") msg.ParseMode = "markdown" _, err := bot.Send(msg)@@ -75,7 +72,7 @@
func TestSendWithMessageReply(t *testing.T) { bot, _ := getBot(t) - msg := tgbotapi.NewMessage(ChatID, "A test message from the test library in telegram-bot-api") + msg := NewMessage(ChatID, "A test message from the test library in telegram-bot-api") msg.ReplyToMessageID = ReplyToMessageID _, err := bot.Send(msg)@@ -88,7 +85,7 @@
func TestSendWithMessageForward(t *testing.T) { bot, _ := getBot(t) - msg := tgbotapi.NewForward(ChatID, ChatID, ReplyToMessageID) + msg := NewForward(ChatID, ChatID, ReplyToMessageID) _, err := bot.Send(msg) if err != nil {@@ -100,7 +97,7 @@
func TestSendWithNewPhoto(t *testing.T) { bot, _ := getBot(t) - msg := tgbotapi.NewPhotoUpload(ChatID, "tests/image.jpg") + msg := NewPhotoUpload(ChatID, "tests/image.jpg") msg.Caption = "Test" _, err := bot.Send(msg)@@ -114,9 +111,9 @@ func TestSendWithNewPhotoWithFileBytes(t *testing.T) {
bot, _ := getBot(t) data, _ := ioutil.ReadFile("tests/image.jpg") - b := tgbotapi.FileBytes{Name: "image.jpg", Bytes: data} + b := FileBytes{Name: "image.jpg", Bytes: data} - msg := tgbotapi.NewPhotoUpload(ChatID, b) + msg := NewPhotoUpload(ChatID, b) msg.Caption = "Test" _, err := bot.Send(msg)@@ -130,9 +127,9 @@ func TestSendWithNewPhotoWithFileReader(t *testing.T) {
bot, _ := getBot(t) f, _ := os.Open("tests/image.jpg") - reader := tgbotapi.FileReader{Name: "image.jpg", Reader: f, Size: -1} + reader := FileReader{Name: "image.jpg", Reader: f, Size: -1} - msg := tgbotapi.NewPhotoUpload(ChatID, reader) + msg := NewPhotoUpload(ChatID, reader) msg.Caption = "Test" _, err := bot.Send(msg)@@ -145,7 +142,7 @@
func TestSendWithNewPhotoReply(t *testing.T) { bot, _ := getBot(t) - msg := tgbotapi.NewPhotoUpload(ChatID, "tests/image.jpg") + msg := NewPhotoUpload(ChatID, "tests/image.jpg") msg.ReplyToMessageID = ReplyToMessageID _, err := bot.Send(msg)@@ -159,7 +156,7 @@
func TestSendWithExistingPhoto(t *testing.T) { bot, _ := getBot(t) - msg := tgbotapi.NewPhotoShare(ChatID, ExistingPhotoFileID) + msg := NewPhotoShare(ChatID, ExistingPhotoFileID) msg.Caption = "Test" _, err := bot.Send(msg)@@ -172,7 +169,7 @@
func TestSendWithNewDocument(t *testing.T) { bot, _ := getBot(t) - msg := tgbotapi.NewDocumentUpload(ChatID, "tests/image.jpg") + msg := NewDocumentUpload(ChatID, "tests/image.jpg") _, err := bot.Send(msg) if err != nil {@@ -184,7 +181,7 @@
func TestSendWithExistingDocument(t *testing.T) { bot, _ := getBot(t) - msg := tgbotapi.NewDocumentShare(ChatID, ExistingDocumentFileID) + msg := NewDocumentShare(ChatID, ExistingDocumentFileID) _, err := bot.Send(msg) if err != nil {@@ -196,7 +193,7 @@
func TestSendWithNewAudio(t *testing.T) { bot, _ := getBot(t) - msg := tgbotapi.NewAudioUpload(ChatID, "tests/audio.mp3") + msg := NewAudioUpload(ChatID, "tests/audio.mp3") msg.Title = "TEST" msg.Duration = 10 msg.Performer = "TEST"@@ -213,7 +210,7 @@
func TestSendWithExistingAudio(t *testing.T) { bot, _ := getBot(t) - msg := tgbotapi.NewAudioShare(ChatID, ExistingAudioFileID) + msg := NewAudioShare(ChatID, ExistingAudioFileID) msg.Title = "TEST" msg.Duration = 10 msg.Performer = "TEST"@@ -229,7 +226,7 @@
func TestSendWithNewVoice(t *testing.T) { bot, _ := getBot(t) - msg := tgbotapi.NewVoiceUpload(ChatID, "tests/voice.ogg") + msg := NewVoiceUpload(ChatID, "tests/voice.ogg") msg.Duration = 10 _, err := bot.Send(msg)@@ -242,7 +239,7 @@
func TestSendWithExistingVoice(t *testing.T) { bot, _ := getBot(t) - msg := tgbotapi.NewVoiceShare(ChatID, ExistingVoiceFileID) + msg := NewVoiceShare(ChatID, ExistingVoiceFileID) msg.Duration = 10 _, err := bot.Send(msg)@@ -255,7 +252,7 @@
func TestSendWithContact(t *testing.T) { bot, _ := getBot(t) - contact := tgbotapi.NewContact(ChatID, "5551234567", "Test") + contact := NewContact(ChatID, "5551234567", "Test") if _, err := bot.Send(contact); err != nil { t.Error(err)@@ -266,7 +263,7 @@
func TestSendWithLocation(t *testing.T) { bot, _ := getBot(t) - _, err := bot.Send(tgbotapi.NewLocation(ChatID, 40, 40)) + _, err := bot.Send(NewLocation(ChatID, 40, 40)) if err != nil { t.Error(err)@@ -277,7 +274,7 @@
func TestSendWithVenue(t *testing.T) { bot, _ := getBot(t) - venue := tgbotapi.NewVenue(ChatID, "A Test Location", "123 Test Street", 40, 40) + venue := NewVenue(ChatID, "A Test Location", "123 Test Street", 40, 40) if _, err := bot.Send(venue); err != nil { t.Error(err)@@ -288,7 +285,7 @@
func TestSendWithNewVideo(t *testing.T) { bot, _ := getBot(t) - msg := tgbotapi.NewVideoUpload(ChatID, "tests/video.mp4") + msg := NewVideoUpload(ChatID, "tests/video.mp4") msg.Duration = 10 msg.Caption = "TEST"@@ -303,7 +300,7 @@
func TestSendWithExistingVideo(t *testing.T) { bot, _ := getBot(t) - msg := tgbotapi.NewVideoShare(ChatID, ExistingVideoFileID) + msg := NewVideoShare(ChatID, ExistingVideoFileID) msg.Duration = 10 msg.Caption = "TEST"@@ -318,7 +315,7 @@
func TestSendWithNewVideoNote(t *testing.T) { bot, _ := getBot(t) - msg := tgbotapi.NewVideoNoteUpload(ChatID, 240, "tests/videonote.mp4") + msg := NewVideoNoteUpload(ChatID, 240, "tests/videonote.mp4") msg.Duration = 10 _, err := bot.Send(msg)@@ -332,7 +329,7 @@
func TestSendWithExistingVideoNote(t *testing.T) { bot, _ := getBot(t) - msg := tgbotapi.NewVideoNoteShare(ChatID, 240, ExistingVideoNoteFileID) + msg := NewVideoNoteShare(ChatID, 240, ExistingVideoNoteFileID) msg.Duration = 10 _, err := bot.Send(msg)@@ -346,7 +343,7 @@
func TestSendWithNewSticker(t *testing.T) { bot, _ := getBot(t) - msg := tgbotapi.NewStickerUpload(ChatID, "tests/image.jpg") + msg := NewStickerUpload(ChatID, "tests/image.jpg") _, err := bot.Send(msg)@@ -359,7 +356,7 @@
func TestSendWithExistingSticker(t *testing.T) { bot, _ := getBot(t) - msg := tgbotapi.NewStickerShare(ChatID, ExistingStickerFileID) + msg := NewStickerShare(ChatID, ExistingStickerFileID) _, err := bot.Send(msg)@@ -372,8 +369,8 @@
func TestSendWithNewStickerAndKeyboardHide(t *testing.T) { bot, _ := getBot(t) - msg := tgbotapi.NewStickerUpload(ChatID, "tests/image.jpg") - msg.ReplyMarkup = tgbotapi.ReplyKeyboardRemove{ + msg := NewStickerUpload(ChatID, "tests/image.jpg") + msg.ReplyMarkup = ReplyKeyboardRemove{ RemoveKeyboard: true, Selective: false, }@@ -388,8 +385,8 @@
func TestSendWithExistingStickerAndKeyboardHide(t *testing.T) { bot, _ := getBot(t) - msg := tgbotapi.NewStickerShare(ChatID, ExistingStickerFileID) - msg.ReplyMarkup = tgbotapi.ReplyKeyboardRemove{ + msg := NewStickerShare(ChatID, ExistingStickerFileID) + msg.ReplyMarkup = ReplyKeyboardRemove{ RemoveKeyboard: true, Selective: false, }@@ -405,7 +402,9 @@
func TestGetFile(t *testing.T) { bot, _ := getBot(t) - file := tgbotapi.FileConfig{FileID: ExistingPhotoFileID} + file := FileConfig{ + FileID: ExistingPhotoFileID, + } _, err := bot.GetFile(file)@@ -418,7 +417,7 @@
func TestSendChatConfig(t *testing.T) { bot, _ := getBot(t) - _, err := bot.Send(tgbotapi.NewChatAction(ChatID, tgbotapi.ChatTyping)) + _, err := bot.Request(NewChatAction(ChatID, ChatTyping)) if err != nil { t.Error(err)@@ -429,14 +428,14 @@
func TestSendEditMessage(t *testing.T) { bot, _ := getBot(t) - msg, err := bot.Send(tgbotapi.NewMessage(ChatID, "Testing editing.")) + msg, err := bot.Send(NewMessage(ChatID, "Testing editing.")) if err != nil { t.Error(err) t.Fail() } - edit := tgbotapi.EditMessageTextConfig{ - BaseEdit: tgbotapi.BaseEdit{ + edit := EditMessageTextConfig{ + BaseEdit: BaseEdit{ ChatID: ChatID, MessageID: msg.MessageID, },@@ -453,7 +452,7 @@
func TestGetUserProfilePhotos(t *testing.T) { bot, _ := getBot(t) - _, err := bot.GetUserProfilePhotos(tgbotapi.NewUserProfilePhotos(ChatID)) + _, err := bot.GetUserProfilePhotos(NewUserProfilePhotos(ChatID)) if err != nil { t.Error(err) t.Fail()@@ -465,19 +464,22 @@ bot, _ := getBot(t)
time.Sleep(time.Second * 2) - bot.RemoveWebhook() + bot.Request(RemoveWebhookConfig{}) - wh := tgbotapi.NewWebhookWithCert("https://example.com/tgbotapi-test/"+bot.Token, "tests/cert.pem") - _, err := bot.SetWebhook(wh) + wh := NewWebhookWithCert("https://example.com/tgbotapi-test/"+bot.Token, "tests/cert.pem") + _, err := bot.Request(wh) if err != nil { t.Error(err) t.Fail() } + _, err = bot.GetWebhookInfo() + if err != nil { t.Error(err) } - bot.RemoveWebhook() + + bot.Request(RemoveWebhookConfig{}) } func TestSetWebhookWithoutCert(t *testing.T) {@@ -485,65 +487,65 @@ bot, _ := getBot(t)
time.Sleep(time.Second * 2) - bot.RemoveWebhook() + bot.Request(RemoveWebhookConfig{}) - wh := tgbotapi.NewWebhook("https://example.com/tgbotapi-test/" + bot.Token) - _, err := bot.SetWebhook(wh) + wh := NewWebhook("https://example.com/tgbotapi-test/" + bot.Token) + _, err := bot.Request(wh) if err != nil { t.Error(err) t.Fail() } + info, err := bot.GetWebhookInfo() + if err != nil { t.Error(err) } + if info.LastErrorDate != 0 { - t.Errorf("[Telegram callback failed]%s", info.LastErrorMessage) + t.Errorf("failed to set webhook: %s", info.LastErrorMessage) } - bot.RemoveWebhook() + + bot.Request(RemoveWebhookConfig{}) } -func TestUpdatesChan(t *testing.T) { +func TestSendWithMediaGroup(t *testing.T) { bot, _ := getBot(t) - var ucfg tgbotapi.UpdateConfig = tgbotapi.NewUpdate(0) - ucfg.Timeout = 60 - _, err := bot.GetUpdatesChan(ucfg) + cfg := NewMediaGroup(ChatID, []interface{}{ + NewInputMediaPhoto("https://i.imgur.com/unQLJIb.jpg"), + NewInputMediaPhoto("https://i.imgur.com/J5qweNZ.jpg"), + NewInputMediaVideo("https://i.imgur.com/F6RmI24.mp4"), + }) + messages, err := bot.SendMediaGroup(cfg) if err != nil { t.Error(err) - t.Fail() } -} -func TestSendWithMediaGroup(t *testing.T) { - bot, _ := getBot(t) + if messages == nil { + t.Error() + } - cfg := tgbotapi.NewMediaGroup(ChatID, []interface{}{ - tgbotapi.NewInputMediaPhoto("https://i.imgur.com/unQLJIb.jpg"), - tgbotapi.NewInputMediaPhoto("https://i.imgur.com/J5qweNZ.jpg"), - tgbotapi.NewInputMediaVideo("https://i.imgur.com/F6RmI24.mp4"), - }) - _, err := bot.Send(cfg) - if err != nil { - t.Error(err) + if len(messages) != 3 { + t.Error() } } func ExampleNewBotAPI() { - bot, err := tgbotapi.NewBotAPI("MyAwesomeBotToken") + bot, err := NewBotAPI("MyAwesomeBotToken") if err != nil { - log.Panic(err) + panic(err) } bot.Debug = true log.Printf("Authorized on account %s", bot.Self.UserName) - u := tgbotapi.NewUpdate(0) + u := NewUpdate(0) u.Timeout = 60 - updates, err := bot.GetUpdatesChan(u) + updates := bot.GetUpdatesChan(u) // Optional: wait for updates and clear them if you don't want to handle // a large backlog of old messages@@ -557,7 +559,7 @@ }
log.Printf("[%s] %s", update.Message.From.UserName, update.Message.Text) - msg := tgbotapi.NewMessage(update.Message.Chat.ID, update.Message.Text) + msg := NewMessage(update.Message.Chat.ID, update.Message.Text) msg.ReplyToMessageID = update.Message.MessageID bot.Send(msg)@@ -565,26 +567,30 @@ }
} func ExampleNewWebhook() { - bot, err := tgbotapi.NewBotAPI("MyAwesomeBotToken") + bot, err := NewBotAPI("MyAwesomeBotToken") if err != nil { - log.Fatal(err) + panic(err) } bot.Debug = true log.Printf("Authorized on account %s", bot.Self.UserName) - _, err = bot.SetWebhook(tgbotapi.NewWebhookWithCert("https://www.google.com:8443/"+bot.Token, "cert.pem")) + _, err = bot.Request(NewWebhookWithCert("https://www.google.com:8443/"+bot.Token, "cert.pem")) if err != nil { - log.Fatal(err) + panic(err) } + info, err := bot.GetWebhookInfo() + if err != nil { - log.Fatal(err) + panic(err) } + if info.LastErrorDate != 0 { - log.Printf("[Telegram callback failed]%s", info.LastErrorMessage) + 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)@@ -593,35 +599,35 @@ log.Printf("%+v\n", update)
} } -func ExampleAnswerInlineQuery() { - bot, err := tgbotapi.NewBotAPI("MyAwesomeBotToken") // create new bot +func ExampleInlineConfig() { + bot, err := NewBotAPI("MyAwesomeBotToken") // create new bot if err != nil { - log.Panic(err) + panic(err) } log.Printf("Authorized on account %s", bot.Self.UserName) - u := tgbotapi.NewUpdate(0) + u := NewUpdate(0) u.Timeout = 60 - updates, err := bot.GetUpdatesChan(u) + updates := bot.GetUpdatesChan(u) for update := range updates { if update.InlineQuery == nil { // if no inline query, ignore it continue } - article := tgbotapi.NewInlineQueryResultArticle(update.InlineQuery.ID, "Echo", update.InlineQuery.Query) + article := NewInlineQueryResultArticle(update.InlineQuery.ID, "Echo", update.InlineQuery.Query) article.Description = update.InlineQuery.Query - inlineConf := tgbotapi.InlineConfig{ + inlineConf := InlineConfig{ InlineQueryID: update.InlineQuery.ID, IsPersonal: true, CacheTime: 0, Results: []interface{}{article}, } - if _, err := bot.AnswerInlineQuery(inlineConf); err != nil { + if _, err := bot.Request(inlineConf); err != nil { log.Println(err) } }@@ -630,15 +636,15 @@
func TestDeleteMessage(t *testing.T) { bot, _ := getBot(t) - msg := tgbotapi.NewMessage(ChatID, "A test message from the test library in telegram-bot-api") + msg := NewMessage(ChatID, "A test message from the test library in telegram-bot-api") msg.ParseMode = "markdown" message, _ := bot.Send(msg) - deleteMessageConfig := tgbotapi.DeleteMessageConfig{ + deleteMessageConfig := DeleteMessageConfig{ ChatID: message.Chat.ID, MessageID: message.MessageID, } - _, err := bot.DeleteMessage(deleteMessageConfig) + _, err := bot.Request(deleteMessageConfig) if err != nil { t.Error(err)@@ -649,16 +655,16 @@
func TestPinChatMessage(t *testing.T) { bot, _ := getBot(t) - msg := tgbotapi.NewMessage(SupergroupChatID, "A test message from the test library in telegram-bot-api") + msg := NewMessage(SupergroupChatID, "A test message from the test library in telegram-bot-api") msg.ParseMode = "markdown" message, _ := bot.Send(msg) - pinChatMessageConfig := tgbotapi.PinChatMessageConfig{ + pinChatMessageConfig := PinChatMessageConfig{ ChatID: message.Chat.ID, MessageID: message.MessageID, DisableNotification: false, } - _, err := bot.PinChatMessage(pinChatMessageConfig) + _, err := bot.Request(pinChatMessageConfig) if err != nil { t.Error(err)@@ -669,25 +675,61 @@
func TestUnpinChatMessage(t *testing.T) { bot, _ := getBot(t) - msg := tgbotapi.NewMessage(SupergroupChatID, "A test message from the test library in telegram-bot-api") + msg := NewMessage(SupergroupChatID, "A test message from the test library in telegram-bot-api") msg.ParseMode = "markdown" message, _ := bot.Send(msg) // We need pin message to unpin something - pinChatMessageConfig := tgbotapi.PinChatMessageConfig{ + pinChatMessageConfig := PinChatMessageConfig{ ChatID: message.Chat.ID, MessageID: message.MessageID, DisableNotification: false, } - _, err := bot.PinChatMessage(pinChatMessageConfig) + + if _, err := bot.Request(pinChatMessageConfig); err != nil { + t.Error(err) + t.Fail() + } - unpinChatMessageConfig := tgbotapi.UnpinChatMessageConfig{ + unpinChatMessageConfig := UnpinChatMessageConfig{ ChatID: message.Chat.ID, } - _, err = bot.UnpinChatMessage(unpinChatMessageConfig) + + if _, err := bot.Request(unpinChatMessageConfig); err != nil { + t.Error(err) + t.Fail() + } +} + +func TestPolls(t *testing.T) { + bot, _ := getBot(t) + + poll := NewPoll(SupergroupChatID, "Are polls working?", "Yes", "No") + + msg, err := bot.Send(poll) + if err != nil { + t.Error(err) + t.Fail() + } + result, err := bot.StopPoll(NewStopPoll(SupergroupChatID, msg.MessageID)) if err != nil { t.Error(err) + t.Fail() + } + + if result.Question != "Are polls working?" { + t.Error("Poll question did not match") + t.Fail() + } + + if !result.IsClosed { + t.Error("Poll did not end") + t.Fail() + } + + if result.Options[0].Text != "Yes" || result.Options[0].VoterCount != 0 || result.Options[1].Text != "No" || result.Options[1].VoterCount != 0 { + t.Error("Poll options were incorrect") t.Fail() } }
@@ -1,7 +1,6 @@
package tgbotapi import ( - "encoding/json" "io" "net/url" "strconv"@@ -36,8 +35,9 @@ )
// Constant values for ParseMode in MessageConfig const ( - ModeMarkdown = "Markdown" - ModeHTML = "HTML" + ModeMarkdown = "Markdown" + ModeMarkdownV2 = "MarkdownV2" + ModeHTML = "HTML" ) // Library errors@@ -49,14 +49,13 @@ )
// Chattable is any config type that can be sent. type Chattable interface { - values() (url.Values, error) + params() (Params, error) method() string } // Fileable is any config type that can be sent that includes a file. type Fileable interface { Chattable - params() (map[string]string, error) name() string getFile() interface{} useExistingFile() bool@@ -71,31 +70,16 @@ ReplyMarkup interface{}
DisableNotification bool } -// values returns url.Values representation of BaseChat -func (chat *BaseChat) values() (url.Values, error) { - v := url.Values{} - if chat.ChannelUsername != "" { - v.Add("chat_id", chat.ChannelUsername) - } else { - v.Add("chat_id", strconv.FormatInt(chat.ChatID, 10)) - } +func (chat *BaseChat) params() (Params, error) { + params := make(Params) - if chat.ReplyToMessageID != 0 { - v.Add("reply_to_message_id", strconv.Itoa(chat.ReplyToMessageID)) - } + params.AddFirstValid("chat_id", chat.ChatID, chat.ChannelUsername) + params.AddNonZero("reply_to_message_id", chat.ReplyToMessageID) + params.AddBool("disable_notification", chat.DisableNotification) - if chat.ReplyMarkup != nil { - data, err := json.Marshal(chat.ReplyMarkup) - if err != nil { - return v, err - } - - v.Add("reply_markup", string(data)) - } - - v.Add("disable_notification", strconv.FormatBool(chat.DisableNotification)) + err := params.AddInterface("reply_markup", chat.ReplyMarkup) - return v, nil + return params, err } // BaseFile is a base type for all file config types.@@ -108,48 +92,19 @@ MimeType string
FileSize int } -// params returns a map[string]string representation of BaseFile. -func (file BaseFile) params() (map[string]string, error) { - params := make(map[string]string) +func (file BaseFile) params() (Params, error) { + params, err := file.BaseChat.params() - if file.ChannelUsername != "" { - params["chat_id"] = file.ChannelUsername - } else { - params["chat_id"] = strconv.FormatInt(file.ChatID, 10) - } - - if file.ReplyToMessageID != 0 { - params["reply_to_message_id"] = strconv.Itoa(file.ReplyToMessageID) - } - - if file.ReplyMarkup != nil { - data, err := json.Marshal(file.ReplyMarkup) - if err != nil { - return params, err - } - - params["reply_markup"] = string(data) - } - - if file.MimeType != "" { - params["mime_type"] = file.MimeType - } - - if file.FileSize > 0 { - params["file_size"] = strconv.Itoa(file.FileSize) - } - - params["disable_notification"] = strconv.FormatBool(file.DisableNotification) + params.AddNonEmpty("mime_type", file.MimeType) + params.AddNonZero("file_size", file.FileSize) - return params, nil + return params, err } -// getFile returns the file. func (file BaseFile) getFile() interface{} { return file.File } -// useExistingFile returns if the BaseFile has already been uploaded. func (file BaseFile) useExistingFile() bool { return file.UseExisting }@@ -163,29 +118,19 @@ InlineMessageID string
ReplyMarkup *InlineKeyboardMarkup } -func (edit BaseEdit) values() (url.Values, error) { - v := url.Values{} +func (edit BaseEdit) params() (Params, error) { + params := make(Params) - if edit.InlineMessageID == "" { - if edit.ChannelUsername != "" { - v.Add("chat_id", edit.ChannelUsername) - } else { - v.Add("chat_id", strconv.FormatInt(edit.ChatID, 10)) - } - v.Add("message_id", strconv.Itoa(edit.MessageID)) + if edit.InlineMessageID != "" { + params["inline_message_id"] = edit.InlineMessageID } else { - v.Add("inline_message_id", edit.InlineMessageID) + params.AddFirstValid("chat_id", edit.ChatID, edit.ChannelUsername) + params.AddNonZero("message_id", edit.MessageID) } - if edit.ReplyMarkup != nil { - data, err := json.Marshal(edit.ReplyMarkup) - if err != nil { - return v, err - } - v.Add("reply_markup", string(data)) - } + err := params.AddInterface("reply_markup", edit.ReplyMarkup) - return v, nil + return params, err } // MessageConfig contains information about a SendMessage request.@@ -196,22 +141,19 @@ ParseMode string
DisableWebPagePreview bool } -// values returns a url.Values representation of MessageConfig. -func (config MessageConfig) values() (url.Values, error) { - v, err := config.BaseChat.values() +func (config MessageConfig) params() (Params, error) { + params, err := config.BaseChat.params() if err != nil { - return v, err - } - v.Add("text", config.Text) - v.Add("disable_web_page_preview", strconv.FormatBool(config.DisableWebPagePreview)) - if config.ParseMode != "" { - v.Add("parse_mode", config.ParseMode) + return params, err } - return v, nil + params.AddNonEmpty("text", config.Text) + params.AddBool("disable_web_page_preview", config.DisableWebPagePreview) + params.AddNonEmpty("parse_mode", config.ParseMode) + + return params, nil } -// method returns Telegram API method name for sending Message. func (config MessageConfig) method() string { return "sendMessage" }@@ -224,18 +166,18 @@ FromChannelUsername string
MessageID int // required } -// values returns a url.Values representation of ForwardConfig. -func (config ForwardConfig) values() (url.Values, error) { - v, err := config.BaseChat.values() +func (config ForwardConfig) params() (Params, error) { + params, err := config.BaseChat.params() if err != nil { - return v, err + return params, err } - v.Add("from_chat_id", strconv.FormatInt(config.FromChatID, 10)) - v.Add("message_id", strconv.Itoa(config.MessageID)) - return v, nil + + params.AddNonZero64("from_chat_id", config.FromChatID) + params.AddNonZero("message_id", config.MessageID) + + return params, nil } -// method returns Telegram API method name for sending Forward. func (config ForwardConfig) method() string { return "forwardMessage" }@@ -247,44 +189,20 @@ Caption string
ParseMode string } -// Params returns a map[string]string representation of PhotoConfig. -func (config PhotoConfig) 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 -} - -// Values returns a url.Values representation of PhotoConfig. -func (config PhotoConfig) values() (url.Values, error) { - v, err := config.BaseChat.values() - if err != nil { - return v, err - } +func (config PhotoConfig) params() (Params, error) { + params, err := config.BaseFile.params() - v.Add(config.name(), config.FileID) - if config.Caption != "" { - v.Add("caption", config.Caption) - if config.ParseMode != "" { - v.Add("parse_mode", config.ParseMode) - } - } + params.AddNonEmpty(config.name(), config.FileID) + params.AddNonEmpty("caption", config.Caption) + params.AddNonEmpty("parse_mode", config.ParseMode) - return v, nil + return params, err } -// name returns the field name for the Photo. func (config PhotoConfig) name() string { return "photo" } -// method returns Telegram API method name for sending Photo. func (config PhotoConfig) method() string { return "sendPhoto" }@@ -299,64 +217,26 @@ Performer string
Title string } -// values returns a url.Values representation of AudioConfig. -func (config AudioConfig) values() (url.Values, error) { - v, err := config.BaseChat.values() +func (config AudioConfig) params() (Params, error) { + params, err := config.BaseChat.params() if err != nil { - return v, err + return params, err } - v.Add(config.name(), config.FileID) - if config.Duration != 0 { - v.Add("duration", strconv.Itoa(config.Duration)) - } - - if config.Performer != "" { - v.Add("performer", config.Performer) - } - if config.Title != "" { - v.Add("title", config.Title) - } - 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 AudioConfig. -func (config AudioConfig) params() (map[string]string, error) { - params, _ := config.BaseFile.params() - - if config.Duration != 0 { - params["duration"] = strconv.Itoa(config.Duration) - } - - if config.Performer != "" { - params["performer"] = config.Performer - } - if config.Title != "" { - params["title"] = config.Title - } - if config.Caption != "" { - params["caption"] = config.Caption - if config.ParseMode != "" { - params["parse_mode"] = config.ParseMode - } - } + params.AddNonEmpty(config.name(), config.FileID) + params.AddNonZero("duration", config.Duration) + params.AddNonEmpty("performer", config.Performer) + params.AddNonEmpty("title", config.Title) + params.AddNonEmpty("caption", config.Caption) + params.AddNonEmpty("parse_mode", config.ParseMode) return params, nil } -// name returns the field name for the Audio. func (config AudioConfig) name() string { return "audio" } -// method returns Telegram API method name for sending Audio. func (config AudioConfig) method() string { return "sendAudio" }@@ -368,44 +248,20 @@ Caption string
ParseMode string } -// values returns a url.Values representation of DocumentConfig. -func (config DocumentConfig) 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) - if config.ParseMode != "" { - v.Add("parse_mode", config.ParseMode) - } - } - - return v, nil -} - -// params returns a map[string]string representation of DocumentConfig. -func (config DocumentConfig) params() (map[string]string, error) { - params, _ := config.BaseFile.params() +func (config DocumentConfig) params() (Params, error) { + params, err := config.BaseFile.params() - if config.Caption != "" { - params["caption"] = config.Caption - if config.ParseMode != "" { - params["parse_mode"] = config.ParseMode - } - } + params.AddNonEmpty(config.name(), config.FileID) + params.AddNonEmpty("caption", config.Caption) + params.AddNonEmpty("parse_mode", config.ParseMode) - return params, nil + return params, err } -// name returns the field name for the Document. func (config DocumentConfig) name() string { return "document" } -// method returns Telegram API method name for sending Document. func (config DocumentConfig) method() string { return "sendDocument" }@@ -415,31 +271,18 @@ type StickerConfig struct {
BaseFile } -// values returns a url.Values representation of StickerConfig. -func (config StickerConfig) values() (url.Values, error) { - v, err := config.BaseChat.values() - if err != nil { - return v, err - } - - v.Add(config.name(), config.FileID) - - return v, nil -} +func (config StickerConfig) params() (Params, error) { + params, err := config.BaseChat.params() -// params returns a map[string]string representation of StickerConfig. -func (config StickerConfig) params() (map[string]string, error) { - params, _ := config.BaseFile.params() + params.AddNonEmpty(config.name(), config.FileID) - return params, nil + return params, err } -// name returns the field name for the Sticker. func (config StickerConfig) name() string { return "sticker" } -// method returns Telegram API method name for sending Sticker. func (config StickerConfig) method() string { return "sendSticker" }@@ -447,52 +290,28 @@
// VideoConfig contains information about a SendVideo request. type VideoConfig struct { BaseFile - Duration int - Caption string - ParseMode string -} - -// values returns a url.Values representation of VideoConfig. -func (config VideoConfig) 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 + Duration int + Caption string + ParseMode string + SupportsStreaming bool } -// params returns a map[string]string representation of VideoConfig. -func (config VideoConfig) params() (map[string]string, error) { - params, _ := config.BaseFile.params() +func (config VideoConfig) params() (Params, error) { + params, err := config.BaseChat.params() - if config.Caption != "" { - params["caption"] = config.Caption - if config.ParseMode != "" { - params["parse_mode"] = config.ParseMode - } - } + params.AddNonEmpty(config.name(), config.FileID) + params.AddNonZero("duration", config.Duration) + params.AddNonEmpty("caption", config.Caption) + params.AddNonEmpty("parse_mode", config.ParseMode) + params.AddBool("supports_streaming", config.SupportsStreaming) - return params, nil + return params, err } -// name returns the field name for the Video. func (config VideoConfig) name() string { return "video" } -// method returns Telegram API method name for sending Video. func (config VideoConfig) method() string { return "sendVideo" }@@ -505,47 +324,21 @@ 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() +func (config AnimationConfig) params() (Params, error) { + params, err := config.BaseChat.params() - if config.Caption != "" { - params["caption"] = config.Caption - if config.ParseMode != "" { - params["parse_mode"] = config.ParseMode - } - } + params.AddNonEmpty(config.name(), config.FileID) + params.AddNonZero("duration", config.Duration) + params.AddNonEmpty("caption", config.Caption) + params.AddNonEmpty("parse_mode", config.ParseMode) - return params, nil + return params, err } -// 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" }@@ -557,46 +350,20 @@ 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 - } +func (config VideoNoteConfig) params() (Params, error) { + params, err := config.BaseChat.params() - 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) - } + params.AddNonEmpty(config.name(), config.FileID) + params.AddNonZero("duration", config.Duration) + params.AddNonZero("length", config.Length) - return params, nil + return params, err } -// 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" }@@ -609,103 +376,78 @@ ParseMode string
Duration int } -// values returns a url.Values representation of VoiceConfig. -func (config VoiceConfig) 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 VoiceConfig. -func (config VoiceConfig) params() (map[string]string, error) { - params, _ := config.BaseFile.params() +func (config VoiceConfig) params() (Params, error) { + params, err := config.BaseChat.params() - if config.Duration != 0 { - params["duration"] = strconv.Itoa(config.Duration) - } - if config.Caption != "" { - params["caption"] = config.Caption - if config.ParseMode != "" { - params["parse_mode"] = config.ParseMode - } - } + params.AddNonEmpty(config.name(), config.FileID) + params.AddNonZero("duration", config.Duration) + params.AddNonEmpty("caption", config.Caption) + params.AddNonEmpty("parse_mode", config.ParseMode) - return params, nil + return params, err } -// name returns the field name for the Voice. func (config VoiceConfig) name() string { return "voice" } -// method returns Telegram API method name for sending Voice. func (config VoiceConfig) method() string { return "sendVoice" } -// MediaGroupConfig contains information about a sendMediaGroup request. -type MediaGroupConfig struct { +// LocationConfig contains information about a SendLocation request. +type LocationConfig struct { BaseChat - InputMedia []interface{} + Latitude float64 // required + Longitude float64 // required + LivePeriod int // optional } -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 - } +func (config LocationConfig) params() (Params, error) { + params, err := config.BaseChat.params() - v.Add("media", string(data)) + params.AddNonZeroFloat("latitude", config.Latitude) + params.AddNonZeroFloat("longitude", config.Longitude) + params.AddNonZero("live_period", config.LivePeriod) - return v, nil + return params, err } -func (config MediaGroupConfig) method() string { - return "sendMediaGroup" +func (config LocationConfig) method() string { + return "sendLocation" } -// LocationConfig contains information about a SendLocation request. -type LocationConfig struct { - BaseChat +// EditMessageLiveLocationConfig allows you to update a live location. +type EditMessageLiveLocationConfig struct { + BaseEdit Latitude float64 // required Longitude float64 // required } -// values returns a url.Values representation of LocationConfig. -func (config LocationConfig) values() (url.Values, error) { - v, err := config.BaseChat.values() - if err != nil { - return v, err - } +func (config EditMessageLiveLocationConfig) params() (Params, error) { + params, err := config.BaseEdit.params() - v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64)) - v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64)) + params.AddNonZeroFloat("latitude", config.Latitude) + params.AddNonZeroFloat("longitude", config.Longitude) - return v, nil + return params, err } -// method returns Telegram API method name for sending Location. -func (config LocationConfig) method() string { - return "sendLocation" +func (config EditMessageLiveLocationConfig) method() string { + return "editMessageLiveLocation" +} + +// StopMessageLiveLocationConfig stops updating a live location. +type StopMessageLiveLocationConfig struct { + BaseEdit +} + +func (config StopMessageLiveLocationConfig) params() (Params, error) { + return config.BaseEdit.params() +} + +func (config StopMessageLiveLocationConfig) method() string { + return "stopMessageLiveLocation" } // VenueConfig contains information about a SendVenue request.@@ -718,21 +460,16 @@ Address string // required
FoursquareID string } -func (config VenueConfig) values() (url.Values, error) { - v, err := config.BaseChat.values() - if err != nil { - return v, err - } +func (config VenueConfig) params() (Params, error) { + params, err := config.BaseChat.params() - v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64)) - v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64)) - v.Add("title", config.Title) - v.Add("address", config.Address) - if config.FoursquareID != "" { - v.Add("foursquare_id", config.FoursquareID) - } + params.AddNonZeroFloat("latitude", config.Latitude) + params.AddNonZeroFloat("longitude", config.Longitude) + params["title"] = config.Title + params["address"] = config.Address + params.AddNonEmpty("foursquare_id", config.FoursquareID) - return v, nil + return params, err } func (config VenueConfig) method() string {@@ -745,40 +482,70 @@ BaseChat
PhoneNumber string FirstName string LastName string + VCard string } -func (config ContactConfig) values() (url.Values, error) { - v, err := config.BaseChat.values() - if err != nil { - return v, err - } +func (config ContactConfig) params() (Params, error) { + params, err := config.BaseChat.params() - v.Add("phone_number", config.PhoneNumber) - v.Add("first_name", config.FirstName) - v.Add("last_name", config.LastName) + params["phone_number"] = config.PhoneNumber + params["first_name"] = config.FirstName - return v, nil + params.AddNonEmpty("last_name", config.LastName) + params.AddNonEmpty("vcard", config.VCard) + + return params, err } func (config ContactConfig) method() string { return "sendContact" } +// SendPollConfig allows you to send a poll. +type SendPollConfig struct { + BaseChat + Question string + Options []string + IsAnonymous bool + Type string + AllowsMultipleAnswers bool + CorrectOptionID int64 + IsClosed bool +} + +func (config SendPollConfig) params() (Params, error) { + params, err := config.BaseChat.params() + if err != nil { + return params, err + } + + params["question"] = config.Question + err = params.AddInterface("options", config.Options) + params["is_anonymous"] = strconv.FormatBool(config.IsAnonymous) + params.AddNonEmpty("type", config.Type) + params["allows_multiple_answers"] = strconv.FormatBool(config.AllowsMultipleAnswers) + params["correct_option_id"] = strconv.FormatInt(config.CorrectOptionID, 10) + params.AddBool("is_closed", config.IsClosed) + + return params, err +} + +func (SendPollConfig) method() string { + return "sendPoll" +} + // GameConfig allows you to send a game. type GameConfig struct { BaseChat GameShortName string } -func (config GameConfig) values() (url.Values, error) { - v, err := config.BaseChat.values() - if err != nil { - return v, err - } +func (config GameConfig) params() (Params, error) { + params, err := config.BaseChat.params() - v.Add("game_short_name", config.GameShortName) + params["game_short_name"] = config.GameShortName - return v, nil + return params, err } func (config GameConfig) method() string {@@ -797,24 +564,21 @@ MessageID int
InlineMessageID string } -func (config SetGameScoreConfig) values() (url.Values, error) { - v := url.Values{} +func (config SetGameScoreConfig) params() (Params, error) { + params := make(Params) - v.Add("user_id", strconv.Itoa(config.UserID)) - v.Add("score", strconv.Itoa(config.Score)) - if config.InlineMessageID == "" { - if config.ChannelUsername == "" { - v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) - } else { - v.Add("chat_id", config.ChannelUsername) - } - v.Add("message_id", strconv.Itoa(config.MessageID)) + params.AddNonZero("user_id", config.UserID) + params.AddNonZero("scrore", config.Score) + params.AddBool("disable_edit_message", config.DisableEditMessage) + + if config.InlineMessageID != "" { + params["inline_message_id"] = config.InlineMessageID } else { - v.Add("inline_message_id", config.InlineMessageID) + params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername) + params.AddNonZero("message_id", config.MessageID) } - v.Add("disable_edit_message", strconv.FormatBool(config.DisableEditMessage)) - return v, nil + return params, nil } func (config SetGameScoreConfig) method() string {@@ -830,22 +594,19 @@ MessageID int
InlineMessageID string } -func (config GetGameHighScoresConfig) values() (url.Values, error) { - v := url.Values{} +func (config GetGameHighScoresConfig) params() (Params, error) { + params := make(Params) - v.Add("user_id", strconv.Itoa(config.UserID)) - if config.InlineMessageID == "" { - if config.ChannelUsername == "" { - v.Add("chat_id", strconv.Itoa(config.ChatID)) - } else { - v.Add("chat_id", config.ChannelUsername) - } - v.Add("message_id", strconv.Itoa(config.MessageID)) + params.AddNonZero("user_id", config.UserID) + + if config.InlineMessageID != "" { + params["inline_message_id"] = config.InlineMessageID } else { - v.Add("inline_message_id", config.InlineMessageID) + params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername) + params.AddNonZero("message_id", config.MessageID) } - return v, nil + return params, nil } func (config GetGameHighScoresConfig) method() string {@@ -858,17 +619,14 @@ BaseChat
Action string // required } -// values returns a url.Values representation of ChatActionConfig. -func (config ChatActionConfig) values() (url.Values, error) { - v, err := config.BaseChat.values() - if err != nil { - return v, err - } - v.Add("action", config.Action) - return v, nil +func (config ChatActionConfig) params() (Params, error) { + params, err := config.BaseChat.params() + + params["action"] = config.Action + + return params, err } -// method returns Telegram API method name for sending ChatAction. func (config ChatActionConfig) method() string { return "sendChatAction" }@@ -881,17 +639,14 @@ ParseMode string
DisableWebPagePreview bool } -func (config EditMessageTextConfig) values() (url.Values, error) { - v, err := config.BaseEdit.values() - if err != nil { - return v, err - } +func (config EditMessageTextConfig) params() (Params, error) { + params, err := config.BaseEdit.params() - v.Add("text", config.Text) - v.Add("parse_mode", config.ParseMode) - v.Add("disable_web_page_preview", strconv.FormatBool(config.DisableWebPagePreview)) + params["text"] = config.Text + params.AddNonEmpty("parse_mode", config.ParseMode) + params.AddBool("disable_web_page_preview", config.DisableWebPagePreview) - return v, nil + return params, err } func (config EditMessageTextConfig) method() string {@@ -905,41 +660,85 @@ Caption string
ParseMode string } -func (config EditMessageCaptionConfig) values() (url.Values, error) { - v, _ := config.BaseEdit.values() +func (config EditMessageCaptionConfig) params() (Params, error) { + params, err := config.BaseEdit.params() - v.Add("caption", config.Caption) - if config.ParseMode != "" { - v.Add("parse_mode", config.ParseMode) - } + params["caption"] = config.Caption + params.AddNonEmpty("parse_mode", config.ParseMode) - return v, nil + return params, err } func (config EditMessageCaptionConfig) method() string { return "editMessageCaption" } +// EditMessageMediaConfig contains information about editing a message's media. +type EditMessageMediaConfig struct { + BaseEdit + + Media interface{} +} + +func (EditMessageMediaConfig) method() string { + return "editMessageMedia" +} + +func (config EditMessageMediaConfig) params() (Params, error) { + params, err := config.BaseEdit.params() + + params.AddInterface("media", config.Media) + + return params, err +} + // EditMessageReplyMarkupConfig allows you to modify the reply markup // of a message. type EditMessageReplyMarkupConfig struct { BaseEdit } -func (config EditMessageReplyMarkupConfig) values() (url.Values, error) { - return config.BaseEdit.values() +func (config EditMessageReplyMarkupConfig) params() (Params, error) { + return config.BaseEdit.params() } func (config EditMessageReplyMarkupConfig) method() string { return "editMessageReplyMarkup" } +// StopPollConfig allows you to stop a poll sent by the bot. +type StopPollConfig struct { + BaseEdit +} + +func (config StopPollConfig) params() (Params, error) { + return config.BaseEdit.params() +} + +func (StopPollConfig) method() string { + return "stopPoll" +} + // UserProfilePhotosConfig contains information about a // GetUserProfilePhotos request. type UserProfilePhotosConfig struct { UserID int Offset int Limit int +} + +func (UserProfilePhotosConfig) method() string { + return "getUserProfilePhotos" +} + +func (config UserProfilePhotosConfig) params() (Params, error) { + params := make(Params) + + params.AddNonZero("user_id", config.UserID) + params.AddNonZero("offset", config.Offset) + params.AddNonZero("limit", config.Limit) + + return params, nil } // FileConfig has information about a file hosted on Telegram.@@ -947,6 +746,18 @@ type FileConfig struct {
FileID string } +func (FileConfig) method() string { + return "getFile" +} + +func (config FileConfig) params() (Params, error) { + params := make(Params) + + params["file_id"] = config.FileID + + return params, nil +} + // UpdateConfig contains information about a GetUpdates request. type UpdateConfig struct { Offset int@@ -954,11 +765,67 @@ Limit int
Timeout int } +func (UpdateConfig) method() string { + return "getUpdates" +} + +func (config UpdateConfig) params() (Params, error) { + params := make(Params) + + params.AddNonZero("offset", config.Offset) + params.AddNonZero("limit", config.Limit) + params.AddNonZero("timeout", config.Timeout) + + return params, nil +} + // WebhookConfig contains information about a SetWebhook request. type WebhookConfig struct { URL *url.URL Certificate interface{} MaxConnections int + AllowedUpdates []string +} + +func (config WebhookConfig) method() string { + return "setWebhook" +} + +func (config WebhookConfig) params() (Params, error) { + params := make(Params) + + if config.URL != nil { + params["url"] = config.URL.String() + } + + params.AddNonZero("max_connections", config.MaxConnections) + params.AddInterface("allowed_updates", config.AllowedUpdates) + + return params, nil +} + +func (config WebhookConfig) name() string { + return "certificate" +} + +func (config WebhookConfig) getFile() interface{} { + return config.Certificate +} + +func (config WebhookConfig) useExistingFile() bool { + return config.URL != nil +} + +// RemoveWebhookConfig is a helper to remove a webhook. +type RemoveWebhookConfig struct { +} + +func (config RemoveWebhookConfig) method() string { + return "setWebhook" +} + +func (config RemoveWebhookConfig) params() (Params, error) { + return nil, nil } // FileBytes contains information about a set of bytes to upload@@ -988,6 +855,27 @@ SwitchPMText string `json:"switch_pm_text"`
SwitchPMParameter string `json:"switch_pm_parameter"` } +func (config InlineConfig) method() string { + return "answerInlineQuery" +} + +func (config InlineConfig) params() (Params, error) { + params := make(Params) + + params["inline_query_id"] = config.InlineQueryID + params.AddNonZero("cache_time", config.CacheTime) + params.AddBool("is_personal", config.IsPersonal) + params.AddNonEmpty("next_offset", config.NextOffset) + params.AddNonEmpty("switch_pm_text", config.SwitchPMText) + params.AddNonEmpty("switch_pm_parameter", config.SwitchPMParameter) + + if err := params.AddInterface("results", config.Results); err != nil { + return params, err + } + + return params, nil +} + // CallbackConfig contains information on making a CallbackQuery response. type CallbackConfig struct { CallbackQueryID string `json:"callback_query_id"`@@ -997,6 +885,22 @@ URL string `json:"url"`
CacheTime int `json:"cache_time"` } +func (config CallbackConfig) method() string { + return "answerCallbackQuery" +} + +func (config CallbackConfig) params() (Params, error) { + params := make(Params) + + params["callback_query_id"] = config.CallbackQueryID + params.AddNonEmpty("text", config.Text) + params.AddBool("show_alert", config.ShowAlert) + params.AddNonEmpty("url", config.URL) + params.AddNonZero("cache_time", config.CacheTime) + + return params, nil +} + // ChatMemberConfig contains information about a user in a chat for use // with administrative functions such as kicking or unbanning a user. type ChatMemberConfig struct {@@ -1006,33 +910,123 @@ ChannelUsername string
UserID int } +// UnbanChatMemberConfig allows you to unban a user. +type UnbanChatMemberConfig struct { + ChatMemberConfig +} + +func (config UnbanChatMemberConfig) method() string { + return "unbanChatMember" +} + +func (config UnbanChatMemberConfig) params() (Params, error) { + params := make(Params) + + params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername, config.ChannelUsername) + params.AddNonZero("user_id", config.UserID) + + return params, nil +} + // KickChatMemberConfig contains extra fields to kick user type KickChatMemberConfig struct { ChatMemberConfig UntilDate int64 } +func (config KickChatMemberConfig) method() string { + return "kickChatMember" +} + +func (config KickChatMemberConfig) params() (Params, error) { + params := make(Params) + + params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername) + params.AddNonZero("user_id", config.UserID) + params.AddNonZero64("until_date", config.UntilDate) + + return params, nil +} + // RestrictChatMemberConfig contains fields to restrict members of chat type RestrictChatMemberConfig struct { ChatMemberConfig - UntilDate int64 - CanSendMessages *bool - CanSendMediaMessages *bool - CanSendOtherMessages *bool - CanAddWebPagePreviews *bool + UntilDate int64 + Permissions *ChatPermissions +} + +func (config RestrictChatMemberConfig) method() string { + return "restrictChatMember" +} + +func (config RestrictChatMemberConfig) params() (Params, error) { + params := make(Params) + + params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername, config.ChannelUsername) + params.AddNonZero("user_id", config.UserID) + + if err := params.AddInterface("permissions", config.Permissions); err != nil { + return params, err + } + params.AddNonZero64("until_date", config.UntilDate) + + return params, nil } // 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 + CanChangeInfo bool + CanPostMessages bool + CanEditMessages bool + CanDeleteMessages bool + CanInviteUsers bool + CanRestrictMembers bool + CanPinMessages bool + CanPromoteMembers bool +} + +func (config PromoteChatMemberConfig) method() string { + return "promoteChatMember" +} + +func (config PromoteChatMemberConfig) params() (Params, error) { + params := make(Params) + + params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername, config.ChannelUsername) + params.AddNonZero("user_id", config.UserID) + + params.AddBool("can_change_info", config.CanChangeInfo) + params.AddBool("can_post_messages", config.CanPostMessages) + params.AddBool("can_edit_messages", config.CanEditMessages) + params.AddBool("can_delete_messages", config.CanDeleteMessages) + params.AddBool("can_invite_users", config.CanInviteUsers) + params.AddBool("can_restrict_members", config.CanRestrictMembers) + params.AddBool("can_pin_messages", config.CanPinMessages) + params.AddBool("can_promote_members", config.CanPromoteMembers) + + return params, nil +} + +// SetChatAdministratorCustomTitle sets the title of an administrative user +// promoted by the bot for a chat. +type SetChatAdministratorCustomTitle struct { + ChatMemberConfig + CustomTitle string +} + +func (SetChatAdministratorCustomTitle) method() string { + return "setChatAdministratorCustomTitle" +} + +func (config SetChatAdministratorCustomTitle) params() (Params, error) { + params := make(Params) + + params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername, config.ChannelUsername) + params.AddNonZero("user_id", config.UserID) + params.AddNonEmpty("custom_title", config.CustomTitle) + + return params, nil } // ChatConfig contains information about getting information on a chat.@@ -1041,80 +1035,179 @@ ChatID int64
SuperGroupUsername string } -// ChatConfigWithUser contains information about getting information on -// a specific user within a chat. +func (config ChatConfig) params() (Params, error) { + params := make(Params) + + params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername) + + return params, nil +} + +// ChatInfoConfig contains information about getting chat information. +type ChatInfoConfig struct { + ChatConfig +} + +func (ChatInfoConfig) method() string { + return "getChat" +} + +// ChatMemberCountConfig contains information about getting the number of users in a chat. +type ChatMemberCountConfig struct { + ChatConfig +} + +func (ChatMemberCountConfig) method() string { + return "getChatMembersCount" +} + +// ChatAdministratorsConfig contains information about getting chat administrators. +type ChatAdministratorsConfig struct { + ChatConfig +} + +func (ChatAdministratorsConfig) method() string { + return "getChatAdministrators" +} + +// SetChatPermissionsConfig allows you to set default permissions for the +// members in a group. The bot must be an administrator and have rights to +// restrict members. +type SetChatPermissionsConfig struct { + ChatConfig + Permissions *ChatPermissions +} + +func (SetChatPermissionsConfig) method() string { + return "setChatPermissions" +} + +func (config SetChatPermissionsConfig) params() (Params, error) { + params := make(Params) + + params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername) + params.AddInterface("permissions", config.Permissions) + + return params, nil +} + +// ChatInviteLinkConfig contains information about getting a chat link. +// +// Note that generating a new link will revoke any previous links. +type ChatInviteLinkConfig struct { + ChatConfig +} + +func (ChatInviteLinkConfig) method() string { + return "exportChatInviteLink" +} + +func (config ChatInviteLinkConfig) params() (Params, error) { + params := make(Params) + + params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername) + + return params, nil +} + +// LeaveChatConfig allows you to leave a chat. +type LeaveChatConfig struct { + ChatID int64 + ChannelUsername string +} + +func (config LeaveChatConfig) method() string { + return "leaveChat" +} + +func (config LeaveChatConfig) params() (Params, error) { + params := make(Params) + + params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername) + + return params, nil +} + +// ChatConfigWithUser contains information about a chat and a user. type ChatConfigWithUser struct { ChatID int64 SuperGroupUsername string UserID int } +func (config ChatConfigWithUser) params() (Params, error) { + params := make(Params) + + params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername) + params.AddNonZero("user_id", config.UserID) + + return params, nil +} + +// GetChatMemberConfig is information about getting a specific member in a chat. +type GetChatMemberConfig struct { + ChatConfigWithUser +} + +func (GetChatMemberConfig) method() string { + return "getChatMember" +} + // 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 + Title string // required + Description string // required + Payload string // required + ProviderToken string // required + StartParameter string // required + Currency string // required + Prices []LabeledPrice // required + ProviderData string + PhotoURL string + PhotoSize int + PhotoWidth int + PhotoHeight int + NeedName bool + NeedPhoneNumber bool + NeedEmail bool + NeedShippingAddress bool + SendPhoneNumberToProvider bool + SendEmailToProvider 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) +func (config InvoiceConfig) params() (Params, error) { + params, err := config.BaseChat.params() 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)) + return params, err } - if config.IsFlexible != false { - v.Add("is_flexible", strconv.FormatBool(config.IsFlexible)) + + params["title"] = config.Title + params["description"] = config.Description + params["payload"] = config.Payload + params["provider_token"] = config.ProviderToken + params["start_parameter"] = config.StartParameter + params["currency"] = config.Currency + + if err = params.AddInterface("prices", config.Prices); err != nil { + return params, err } - return v, nil + params.AddNonEmpty("provider_data", config.ProviderData) + params.AddNonEmpty("photo_url", config.PhotoURL) + params.AddNonZero("photo_size", config.PhotoSize) + params.AddNonZero("photo_width", config.PhotoWidth) + params.AddNonZero("photo_height", config.PhotoHeight) + params.AddBool("need_name", config.NeedName) + params.AddBool("need_phone_number", config.NeedPhoneNumber) + params.AddBool("need_email", config.NeedEmail) + params.AddBool("need_shipping_address", config.NeedShippingAddress) + params.AddBool("is_flexible", config.IsFlexible) + params.AddBool("send_phone_number_to_provider", config.SendPhoneNumberToProvider) + params.AddBool("send_email_to_provider", config.SendEmailToProvider) + + return params, nil } func (config InvoiceConfig) method() string {@@ -1125,7 +1218,7 @@ // ShippingConfig contains information for answerShippingQuery request.
type ShippingConfig struct { ShippingQueryID string // required OK bool // required - ShippingOptions *[]ShippingOption + ShippingOptions []ShippingOption ErrorMessage string }@@ -1146,18 +1239,19 @@ func (config DeleteMessageConfig) method() string {
return "deleteMessage" } -func (config DeleteMessageConfig) values() (url.Values, error) { - v := url.Values{} +func (config DeleteMessageConfig) params() (Params, error) { + params := make(Params) - v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) - v.Add("message_id", strconv.Itoa(config.MessageID)) + params.AddNonZero64("chat_id", config.ChatID) + params.AddNonZero("message_id", config.MessageID) - return v, nil + return params, nil } // PinChatMessageConfig contains information of a message in a chat to pin. type PinChatMessageConfig struct { ChatID int64 + ChannelUsername string MessageID int DisableNotification bool }@@ -1166,55 +1260,99 @@ func (config PinChatMessageConfig) method() string {
return "pinChatMessage" } -func (config PinChatMessageConfig) values() (url.Values, error) { - v := url.Values{} +func (config PinChatMessageConfig) params() (Params, error) { + params := make(Params) - 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)) + params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername) + params.AddNonZero("message_id", config.MessageID) + params.AddBool("disable_notification", config.DisableNotification) - return v, nil + return params, nil } // UnpinChatMessageConfig contains information of chat to unpin. type UnpinChatMessageConfig struct { - ChatID int64 + ChatID int64 + ChannelUsername string } func (config UnpinChatMessageConfig) method() string { return "unpinChatMessage" } -func (config UnpinChatMessageConfig) values() (url.Values, error) { - v := url.Values{} +func (config UnpinChatMessageConfig) params() (Params, error) { + params := make(Params) - v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) + params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername) - return v, nil + return params, nil } -// SetChatTitleConfig contains information for change chat title. +// SetChatPhotoConfig allows you to set a group, supergroup, or channel's photo. +type SetChatPhotoConfig struct { + BaseFile +} + +func (config SetChatPhotoConfig) method() string { + return "setChatPhoto" +} + +func (config SetChatPhotoConfig) name() string { + return "photo" +} + +func (config SetChatPhotoConfig) getFile() interface{} { + return config.File +} + +func (config SetChatPhotoConfig) useExistingFile() bool { + return config.UseExisting +} + +// DeleteChatPhotoConfig allows you to delete a group, supergroup, or channel's photo. +type DeleteChatPhotoConfig struct { + ChatID int64 + ChannelUsername string +} + +func (config DeleteChatPhotoConfig) method() string { + return "deleteChatPhoto" +} + +func (config DeleteChatPhotoConfig) params() (Params, error) { + params := make(Params) + + params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername) + + return params, nil +} + +// SetChatTitleConfig allows you to set the title of something other than a private chat. type SetChatTitleConfig struct { - ChatID int64 - Title string + ChatID int64 + ChannelUsername string + + Title string } func (config SetChatTitleConfig) method() string { return "setChatTitle" } -func (config SetChatTitleConfig) values() (url.Values, error) { - v := url.Values{} +func (config SetChatTitleConfig) params() (Params, error) { + params := make(Params) - v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) - v.Add("title", config.Title) + params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername) + params["title"] = config.Title - return v, nil + return params, nil } -// SetChatDescriptionConfig contains information for change chat description. +// SetChatDescriptionConfig allows you to set the description of a supergroup or channel. type SetChatDescriptionConfig struct { - ChatID int64 + ChatID int64 + ChannelUsername string + Description string }@@ -1222,43 +1360,252 @@ func (config SetChatDescriptionConfig) method() string {
return "setChatDescription" } -func (config SetChatDescriptionConfig) values() (url.Values, error) { - v := url.Values{} +func (config SetChatDescriptionConfig) params() (Params, error) { + params := make(Params) - v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) - v.Add("description", config.Description) + params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername) + params["description"] = config.Description - return v, nil + return params, nil } -// SetChatPhotoConfig contains information for change chat photo -type SetChatPhotoConfig struct { - BaseFile +// GetStickerSetConfig allows you to get the stickers in a set. +type GetStickerSetConfig struct { + Name string } -// name returns the field name for the Photo. -func (config SetChatPhotoConfig) name() string { - return "photo" +func (config GetStickerSetConfig) method() string { + return "getStickerSet" } -// method returns Telegram API method name for sending Photo. -func (config SetChatPhotoConfig) method() string { - return "setChatPhoto" +func (config GetStickerSetConfig) params() (Params, error) { + params := make(Params) + + params["name"] = config.Name + + return params, nil } -// DeleteChatPhotoConfig contains information for delete chat photo. -type DeleteChatPhotoConfig struct { - ChatID int64 +// UploadStickerConfig allows you to upload a sticker for use in a set later. +type UploadStickerConfig struct { + UserID int64 + PNGSticker interface{} } -func (config DeleteChatPhotoConfig) method() string { - return "deleteChatPhoto" +func (config UploadStickerConfig) method() string { + return "uploadStickerFile" } -func (config DeleteChatPhotoConfig) values() (url.Values, error) { - v := url.Values{} +func (config UploadStickerConfig) params() (Params, error) { + params := make(Params) - v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) + params.AddNonZero64("user_id", config.UserID) - return v, nil + return params, nil +} + +func (config UploadStickerConfig) name() string { + return "png_sticker" +} + +func (config UploadStickerConfig) getFile() interface{} { + return config.PNGSticker +} + +func (config UploadStickerConfig) useExistingFile() bool { + return false +} + +// NewStickerSetConfig allows creating a new sticker set. +type NewStickerSetConfig struct { + UserID int64 + Name string + Title string + PNGSticker interface{} + Emojis string + ContainsMasks bool + MaskPosition *MaskPosition +} + +func (config NewStickerSetConfig) method() string { + return "createNewStickerSet" +} + +func (config NewStickerSetConfig) params() (Params, error) { + params := make(Params) + + params.AddNonZero64("user_id", config.UserID) + params["name"] = config.Name + params["title"] = config.Title + + if sticker, ok := config.PNGSticker.(string); ok { + params[config.name()] = sticker + } + + params["emojis"] = config.Emojis + + params.AddBool("contains_masks", config.ContainsMasks) + + err := params.AddInterface("mask_position", config.MaskPosition) + + return params, err +} + +func (config NewStickerSetConfig) getFile() interface{} { + return config.PNGSticker +} + +func (config NewStickerSetConfig) name() string { + return "png_sticker" +} + +func (config NewStickerSetConfig) useExistingFile() bool { + _, ok := config.PNGSticker.(string) + + return ok +} + +// AddStickerConfig allows you to add a sticker to a set. +type AddStickerConfig struct { + UserID int64 + Name string + PNGSticker interface{} + Emojis string + MaskPosition *MaskPosition +} + +func (config AddStickerConfig) method() string { + return "addStickerToSet" +} + +func (config AddStickerConfig) params() (Params, error) { + params := make(Params) + + params.AddNonZero64("user_id", config.UserID) + params["name"] = config.Name + params["emojis"] = config.Emojis + + if sticker, ok := config.PNGSticker.(string); ok { + params[config.name()] = sticker + } + + err := params.AddInterface("mask_position", config.MaskPosition) + + return params, err +} + +func (config AddStickerConfig) name() string { + return "png_sticker" +} + +func (config AddStickerConfig) getFile() interface{} { + return config.PNGSticker +} + +func (config AddStickerConfig) useExistingFile() bool { + return false +} + +// SetStickerPositionConfig allows you to change the position of a sticker in a set. +type SetStickerPositionConfig struct { + Sticker string + Position int +} + +func (config SetStickerPositionConfig) method() string { + return "setStickerPositionInSet" +} + +func (config SetStickerPositionConfig) params() (Params, error) { + params := make(Params) + + params["sticker"] = config.Sticker + params.AddNonZero("position", config.Position) + + return params, nil +} + +// DeleteStickerConfig allows you to delete a sticker from a set. +type DeleteStickerConfig struct { + Sticker string +} + +func (config DeleteStickerConfig) method() string { + return "deleteStickerFromSet" +} + +func (config DeleteStickerConfig) params() (Params, error) { + params := make(Params) + + params["sticker"] = config.Sticker + + return params, nil +} + +// SetChatStickerSetConfig allows you to set the sticker set for a supergroup. +type SetChatStickerSetConfig struct { + ChatID int64 + SuperGroupUsername string + + StickerSetName string +} + +func (config SetChatStickerSetConfig) method() string { + return "setChatStickerSet" +} + +func (config SetChatStickerSetConfig) params() (Params, error) { + params := make(Params) + + params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername) + params["sticker_set_name"] = config.StickerSetName + + return params, nil +} + +// DeleteChatStickerSetConfig allows you to remove a supergroup's sticker set. +type DeleteChatStickerSetConfig struct { + ChatID int64 + SuperGroupUsername string +} + +func (config DeleteChatStickerSetConfig) method() string { + return "deleteChatStickerSet" +} + +func (config DeleteChatStickerSetConfig) params() (Params, error) { + params := make(Params) + + params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername) + + return params, 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{} + DisableNotification bool + ReplyToMessageID int +} + +func (config MediaGroupConfig) method() string { + return "sendMediaGroup" +} + +func (config MediaGroupConfig) params() (Params, error) { + params := make(Params) + + params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername) + if err := params.AddInterface("media", config.Media); err != nil { + return params, nil + } + params.AddBool("disable_notification", config.DisableNotification) + params.AddNonZero("reply_to_message_id", config.ReplyToMessageID) + + return params, nil }
@@ -0,0 +1,8 @@
+module github.com/go-telegram-bot-api/telegram-bot-api/v5 + +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
@@ -0,0 +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=
@@ -294,26 +294,58 @@ // 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, + ChatID: chatID, + Media: files, } } // NewInputMediaPhoto creates a new InputMediaPhoto. func NewInputMediaPhoto(media string) InputMediaPhoto { return InputMediaPhoto{ - Type: "photo", - Media: media, + BaseInputMedia{ + Type: "photo", + Media: media, + }, } } // NewInputMediaVideo creates a new InputMediaVideo. func NewInputMediaVideo(media string) InputMediaVideo { return InputMediaVideo{ - Type: "video", - Media: media, + BaseInputMedia: BaseInputMedia{ + Type: "video", + Media: media, + }, + } +} + +// NewInputMediaAnimation creates a new InputMediaAnimation. +func NewInputMediaAnimation(media string) InputMediaAnimation { + return InputMediaAnimation{ + BaseInputMedia: BaseInputMedia{ + Type: "animation", + Media: media, + }, + } +} + +// NewInputMediaAudio creates a new InputMediaAudio. +func NewInputMediaAudio(media string) InputMediaAudio { + return InputMediaAudio{ + BaseInputMedia: BaseInputMedia{ + Type: "audio", + Media: media, + }, + } +} + +// NewInputMediaDocument creates a new InputMediaDocument. +func NewInputMediaDocument(media string) InputMediaDocument { + return InputMediaDocument{ + BaseInputMedia: BaseInputMedia{ + Type: "document", + Media: media, + }, } }@@ -783,7 +815,7 @@ }
} // NewInvoice creates a new Invoice request to the user. -func NewInvoice(chatID int64, title, description, payload, providerToken, startParameter, currency string, prices *[]LabeledPrice) InvoiceConfig { +func NewInvoice(chatID int64, title, description, payload, providerToken, startParameter, currency string, prices []LabeledPrice) InvoiceConfig { return InvoiceConfig{ BaseChat: BaseChat{ChatID: chatID}, Title: title,@@ -825,3 +857,60 @@ UseExisting: true,
}, } } + +// NewChatTitle allows you to update the title of a chat. +func NewChatTitle(chatID int64, title string) SetChatTitleConfig { + return SetChatTitleConfig{ + ChatID: chatID, + Title: title, + } +} + +// NewChatDescription allows you to update the description of a chat. +func NewChatDescription(chatID int64, description string) SetChatDescriptionConfig { + return SetChatDescriptionConfig{ + ChatID: chatID, + Description: description, + } +} + +// NewChatPhoto allows you to update the photo for a chat. +func NewChatPhoto(chatID int64, photo interface{}) SetChatPhotoConfig { + return SetChatPhotoConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ + ChatID: chatID, + }, + File: photo, + }, + } +} + +// NewDeleteChatPhoto allows you to delete the photo for a chat. +func NewDeleteChatPhoto(chatID int64, photo interface{}) DeleteChatPhotoConfig { + return DeleteChatPhotoConfig{ + ChatID: chatID, + } +} + +// NewPoll allows you to create a new poll. +func NewPoll(chatID int64, question string, options ...string) SendPollConfig { + return SendPollConfig{ + BaseChat: BaseChat{ + ChatID: chatID, + }, + Question: question, + Options: options, + IsAnonymous: true, // This is Telegram's default. + } +} + +// NewStopPoll allows you to stop a poll. +func NewStopPoll(chatID int64, messageID int) StopPollConfig { + return StopPollConfig{ + BaseEdit{ + ChatID: chatID, + MessageID: messageID, + }, + } +}
@@ -1,47 +1,46 @@
-package tgbotapi_test +package tgbotapi import ( - "github.com/go-telegram-bot-api/telegram-bot-api" "testing" ) func TestNewInlineQueryResultArticle(t *testing.T) { - result := tgbotapi.NewInlineQueryResultArticle("id", "title", "message") + result := NewInlineQueryResultArticle("id", "title", "message") if result.Type != "article" || result.ID != "id" || result.Title != "title" || - result.InputMessageContent.(tgbotapi.InputTextMessageContent).Text != "message" { + result.InputMessageContent.(InputTextMessageContent).Text != "message" { t.Fail() } } func TestNewInlineQueryResultArticleMarkdown(t *testing.T) { - result := tgbotapi.NewInlineQueryResultArticleMarkdown("id", "title", "*message*") + result := NewInlineQueryResultArticleMarkdown("id", "title", "*message*") if result.Type != "article" || result.ID != "id" || result.Title != "title" || - result.InputMessageContent.(tgbotapi.InputTextMessageContent).Text != "*message*" || - result.InputMessageContent.(tgbotapi.InputTextMessageContent).ParseMode != "Markdown" { + result.InputMessageContent.(InputTextMessageContent).Text != "*message*" || + result.InputMessageContent.(InputTextMessageContent).ParseMode != "Markdown" { t.Fail() } } func TestNewInlineQueryResultArticleHTML(t *testing.T) { - result := tgbotapi.NewInlineQueryResultArticleHTML("id", "title", "<b>message</b>") + result := NewInlineQueryResultArticleHTML("id", "title", "<b>message</b>") if result.Type != "article" || result.ID != "id" || result.Title != "title" || - result.InputMessageContent.(tgbotapi.InputTextMessageContent).Text != "<b>message</b>" || - result.InputMessageContent.(tgbotapi.InputTextMessageContent).ParseMode != "HTML" { + result.InputMessageContent.(InputTextMessageContent).Text != "<b>message</b>" || + result.InputMessageContent.(InputTextMessageContent).ParseMode != "HTML" { t.Fail() } } func TestNewInlineQueryResultGIF(t *testing.T) { - result := tgbotapi.NewInlineQueryResultGIF("id", "google.com") + result := NewInlineQueryResultGIF("id", "google.com") if result.Type != "gif" || result.ID != "id" ||@@ -51,7 +50,7 @@ }
} func TestNewInlineQueryResultMPEG4GIF(t *testing.T) { - result := tgbotapi.NewInlineQueryResultMPEG4GIF("id", "google.com") + result := NewInlineQueryResultMPEG4GIF("id", "google.com") if result.Type != "mpeg4_gif" || result.ID != "id" ||@@ -61,7 +60,7 @@ }
} func TestNewInlineQueryResultPhoto(t *testing.T) { - result := tgbotapi.NewInlineQueryResultPhoto("id", "google.com") + result := NewInlineQueryResultPhoto("id", "google.com") if result.Type != "photo" || result.ID != "id" ||@@ -71,7 +70,7 @@ }
} func TestNewInlineQueryResultPhotoWithThumb(t *testing.T) { - result := tgbotapi.NewInlineQueryResultPhotoWithThumb("id", "google.com", "thumb.com") + result := NewInlineQueryResultPhotoWithThumb("id", "google.com", "thumb.com") if result.Type != "photo" || result.ID != "id" ||@@ -82,7 +81,7 @@ }
} func TestNewInlineQueryResultVideo(t *testing.T) { - result := tgbotapi.NewInlineQueryResultVideo("id", "google.com") + result := NewInlineQueryResultVideo("id", "google.com") if result.Type != "video" || result.ID != "id" ||@@ -92,7 +91,7 @@ }
} func TestNewInlineQueryResultAudio(t *testing.T) { - result := tgbotapi.NewInlineQueryResultAudio("id", "google.com", "title") + result := NewInlineQueryResultAudio("id", "google.com", "title") if result.Type != "audio" || result.ID != "id" ||@@ -103,7 +102,7 @@ }
} func TestNewInlineQueryResultVoice(t *testing.T) { - result := tgbotapi.NewInlineQueryResultVoice("id", "google.com", "title") + result := NewInlineQueryResultVoice("id", "google.com", "title") if result.Type != "voice" || result.ID != "id" ||@@ -114,7 +113,7 @@ }
} func TestNewInlineQueryResultDocument(t *testing.T) { - result := tgbotapi.NewInlineQueryResultDocument("id", "google.com", "title", "mime/type") + result := NewInlineQueryResultDocument("id", "google.com", "title", "mime/type") if result.Type != "document" || result.ID != "id" ||@@ -126,7 +125,7 @@ }
} func TestNewInlineQueryResultLocation(t *testing.T) { - result := tgbotapi.NewInlineQueryResultLocation("id", "name", 40, 50) + result := NewInlineQueryResultLocation("id", "name", 40, 50) if result.Type != "location" || result.ID != "id" ||@@ -138,7 +137,7 @@ }
} func TestNewEditMessageText(t *testing.T) { - edit := tgbotapi.NewEditMessageText(ChatID, ReplyToMessageID, "new text") + edit := NewEditMessageText(ChatID, ReplyToMessageID, "new text") if edit.Text != "new text" || edit.BaseEdit.ChatID != ChatID ||@@ -148,7 +147,7 @@ }
} func TestNewEditMessageCaption(t *testing.T) { - edit := tgbotapi.NewEditMessageCaption(ChatID, ReplyToMessageID, "new caption") + edit := NewEditMessageCaption(ChatID, ReplyToMessageID, "new caption") if edit.Caption != "new caption" || edit.BaseEdit.ChatID != ChatID ||@@ -158,15 +157,15 @@ }
} func TestNewEditMessageReplyMarkup(t *testing.T) { - markup := tgbotapi.InlineKeyboardMarkup{ - InlineKeyboard: [][]tgbotapi.InlineKeyboardButton{ - []tgbotapi.InlineKeyboardButton{ - tgbotapi.InlineKeyboardButton{Text: "test"}, + markup := InlineKeyboardMarkup{ + InlineKeyboard: [][]InlineKeyboardButton{ + []InlineKeyboardButton{ + InlineKeyboardButton{Text: "test"}, }, }, } - edit := tgbotapi.NewEditMessageReplyMarkup(ChatID, ReplyToMessageID, markup) + edit := NewEditMessageReplyMarkup(ChatID, ReplyToMessageID, markup) if edit.ReplyMarkup.InlineKeyboard[0][0].Text != "test" || edit.BaseEdit.ChatID != ChatID ||
@@ -0,0 +1,97 @@
+package tgbotapi + +import ( + "encoding/json" + "reflect" + "strconv" +) + +// Params represents a set of parameters that gets passed to a request. +type Params map[string]string + +// AddNonEmpty adds a value if it not an empty string. +func (p Params) AddNonEmpty(key, value string) { + if value != "" { + p[key] = value + } +} + +// AddNonZero adds a value if it is not zero. +func (p Params) AddNonZero(key string, value int) { + if value != 0 { + p[key] = strconv.Itoa(value) + } +} + +// AddNonZero64 is the same as AddNonZero except uses an int64. +func (p Params) AddNonZero64(key string, value int64) { + if value != 0 { + p[key] = strconv.FormatInt(value, 10) + } +} + +// AddBool adds a value of a bool if it is true. +func (p Params) AddBool(key string, value bool) { + if value { + p[key] = strconv.FormatBool(value) + } +} + +// AddNonZeroFloat adds a floating point value that is not zero. +func (p Params) AddNonZeroFloat(key string, value float64) { + if value != 0 { + p[key] = strconv.FormatFloat(value, 'f', 6, 64) + } +} + +// AddInterface adds an interface if it is not nill and can be JSON marshalled. +func (p Params) AddInterface(key string, value interface{}) error { + if value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) { + return nil + } + + b, err := json.Marshal(value) + if err != nil { + return err + } + + p[key] = string(b) + + return nil +} + +// AddFirstValid attempts to add the first item that is not a default value. +// +// For example, AddFirstValid(0, "", "test") would add "test". +func (p Params) AddFirstValid(key string, args ...interface{}) error { + for _, arg := range args { + switch v := arg.(type) { + case int: + if v != 0 { + p[key] = strconv.Itoa(v) + return nil + } + case int64: + if v != 0 { + p[key] = strconv.FormatInt(v, 10) + return nil + } + case string: + if v != "" { + p[key] = v + return nil + } + case nil: + default: + b, err := json.Marshal(arg) + if err != nil { + return err + } + + p[key] = string(b) + return nil + } + } + + return nil +}
@@ -0,0 +1,93 @@
+package tgbotapi + +import ( + "testing" +) + +func assertLen(t *testing.T, params Params, l int) { + actual := len(params) + if actual != l { + t.Fatalf("Incorrect number of params, expected %d but found %d\n", l, actual) + } +} + +func assertEq(t *testing.T, a interface{}, b interface{}) { + if a != b { + t.Fatalf("Values did not match, a: %v, b: %v\n", a, b) + } +} + +func TestAddNonEmpty(t *testing.T) { + params := make(Params) + params.AddNonEmpty("value", "value") + assertLen(t, params, 1) + assertEq(t, params["value"], "value") + params.AddNonEmpty("test", "") + assertLen(t, params, 1) + assertEq(t, params["test"], "") +} + +func TestAddNonZero(t *testing.T) { + params := make(Params) + params.AddNonZero("value", 1) + assertLen(t, params, 1) + assertEq(t, params["value"], "1") + params.AddNonZero("test", 0) + assertLen(t, params, 1) + assertEq(t, params["test"], "") +} + +func TestAddNonZero64(t *testing.T) { + params := make(Params) + params.AddNonZero64("value", 1) + assertLen(t, params, 1) + assertEq(t, params["value"], "1") + params.AddNonZero64("test", 0) + assertLen(t, params, 1) + assertEq(t, params["test"], "") +} + +func TestAddBool(t *testing.T) { + params := make(Params) + params.AddBool("value", true) + assertLen(t, params, 1) + assertEq(t, params["value"], "true") + params.AddBool("test", false) + assertLen(t, params, 1) + assertEq(t, params["test"], "") +} + +func TestAddNonZeroFloat(t *testing.T) { + params := make(Params) + params.AddNonZeroFloat("value", 1) + assertLen(t, params, 1) + assertEq(t, params["value"], "1.000000") + params.AddNonZeroFloat("test", 0) + assertLen(t, params, 1) + assertEq(t, params["test"], "") +} + +func TestAddInterface(t *testing.T) { + params := make(Params) + data := struct { + Name string `json:"name"` + }{ + Name: "test", + } + params.AddInterface("value", data) + assertLen(t, params, 1) + assertEq(t, params["value"], `{"name":"test"}`) + params.AddInterface("test", nil) + assertLen(t, params, 1) + assertEq(t, params["test"], "") +} + +func TestAddFirstValid(t *testing.T) { + params := make(Params) + params.AddFirstValid("value", 0, "", "test") + assertLen(t, params, 1) + assertEq(t, params["value"], "test") + params.AddFirstValid("value2", 3, "test") + assertLen(t, params, 2) + assertEq(t, params["value2"], "3") +}
@@ -61,6 +61,8 @@ PassportFile struct {
// Unique identifier for this file FileID string `json:"file_id"` + FileUniqueID string `json:"file_unique_id"` + // File size FileSize int `json:"file_size"`
@@ -37,6 +37,8 @@ ChosenInlineResult *ChosenInlineResult `json:"chosen_inline_result"`
CallbackQuery *CallbackQuery `json:"callback_query"` ShippingQuery *ShippingQuery `json:"shipping_query"` PreCheckoutQuery *PreCheckoutQuery `json:"pre_checkout_query"` + Poll *Poll `json:"poll"` + PollAnswer *PollAnswer `json:"poll_answer"` } // UpdatesChannel is the channel for getting updates.@@ -51,12 +53,15 @@ }
// 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 - LanguageCode string `json:"language_code"` // optional - IsBot bool `json:"is_bot"` // 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 + CanJoinGroups bool `json:"can_join_groups"` // optional + CanReadAllGroupMessages bool `json:"can_read_all_group_messages"` // optional + SupportsInlineQueries bool `json:"supports_inline_queries"` // optional } // String displays a simple text version of a user.@@ -84,23 +89,42 @@ }
// ChatPhoto represents a chat photo. type ChatPhoto struct { - SmallFileID string `json:"small_file_id"` - BigFileID string `json:"big_file_id"` + SmallFileID string `json:"small_file_id"` + SmallFileUniqueID string `json:"small_file_unique_id"` + BigFileID string `json:"big_file_id"` + BigFileUniqueID string `json:"big_file_unique_id"` +} + +// ChatPermissions describes actions that a non-administrator user is +// allowed to take in a chat. All fields are optional. +type ChatPermissions struct { + CanSendMessages bool `json:"can_send_messages"` + CanSendMediaMessages bool `json:"can_send_media_messages"` + CanSendPolls bool `json:"can_send_polls"` + CanSendOtherMessages bool `json:"can_send_other_messages"` + CanAddWebPagePreviews bool `json:"can_add_web_page_previews"` + CanChangeInfo bool `json:"can_change_info"` + CanInviteUsers bool `json:"can_invite_users"` + CanPinMessages bool `json:"can_pin_messages"` } // 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 - Photo *ChatPhoto `json:"photo"` - Description string `json:"description,omitempty"` // optional - InviteLink string `json:"invite_link,omitempty"` // optional - PinnedMessage *Message `json:"pinned_message"` // 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"` // deprecated, optional + Photo *ChatPhoto `json:"photo"` // optional + Description string `json:"description,omitempty"` // optional + InviteLink string `json:"invite_link,omitempty"` // optional + PinnedMessage *Message `json:"pinned_message"` // optional + Permissions *ChatPermissions `json:"permissions"` // optional + SlowModeDelay int `json:"slow_mode_delay"` // optional + StickerSetName string `json:"sticker_set_name"` // optional + CanSetStickerSet bool `json:"can_set_sticker_set"` // optional } // IsPrivate returns if the Chat is a private conversation.@@ -131,46 +155,53 @@
// 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 - CaptionEntities *[]MessageEntity `json:"caption_entities"` // 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 - 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 - PassportData *PassportData `json:"passport_data,omitempty"` // 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 + ForwardSignature string `json:"forward_signature"` // optional + ForwardSenderName string `json:"forward_sender_name"` // optional + ForwardDate int `json:"forward_date"` // optional + ReplyToMessage *Message `json:"reply_to_message"` // optional + EditDate int `json:"edit_date"` // optional + MediaGroupID string `json:"media_group_id"` // optional + AuthorSignature string `json:"author_signature"` // optional + Text string `json:"text"` // optional + Entities []MessageEntity `json:"entities"` // optional + CaptionEntities []MessageEntity `json:"caption_entities"` // 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 + 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 + Poll *Poll `json:"poll"` // 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 + ConnectedWebsite string `json:"connected_website"` // optional + PassportData *PassportData `json:"passport_data,omitempty"` // optional + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup"` // optional } // Time converts the message timestamp into a Time.@@ -180,11 +211,11 @@ }
// IsCommand returns true if message starts with a "bot_command" entity. func (m *Message) IsCommand() bool { - if m.Entities == nil || len(*m.Entities) == 0 { + if m.Entities == nil || len(m.Entities) == 0 { return false } - entity := (*m.Entities)[0] + entity := m.Entities[0] return entity.Offset == 0 && entity.IsCommand() }@@ -214,7 +245,7 @@ return ""
} // IsCommand() checks that the message begins with a bot_command entity - entity := (*m.Entities)[0] + entity := m.Entities[0] return m.Text[1:entity.Length] }@@ -233,7 +264,8 @@ return ""
} // IsCommand() checks that the message begins with a bot_command entity - entity := (*m.Entities)[0] + entity := m.Entities[0] + if len(m.Text) == entity.Length { return "" // The command makes up the whole message }@@ -243,11 +275,12 @@ }
// MessageEntity contains information about data in a Message. type MessageEntity struct { - Type string `json:"type"` - Offset int `json:"offset"` - Length int `json:"length"` - URL string `json:"url"` // optional - User *User `json:"user"` // optional + Type string `json:"type"` + Offset int `json:"offset"` + Length int `json:"length"` + URL string `json:"url"` // optional + User *User `json:"user"` // optional + Language string `json:"language"` // optional } // ParseURL attempts to parse a URL contained within a MessageEntity.@@ -311,81 +344,104 @@ }
// PhotoSize contains information about photos. type PhotoSize struct { - FileID string `json:"file_id"` - Width int `json:"width"` - Height int `json:"height"` - FileSize int `json:"file_size"` // optional + FileID string `json:"file_id"` + FileUniqueID string `json:"file_unique_id"` + Width int `json:"width"` + Height int `json:"height"` + FileSize int `json:"file_size"` // optional } // Audio contains information about audio. type Audio struct { - FileID string `json:"file_id"` - Duration int `json:"duration"` - Performer string `json:"performer"` // optional - Title string `json:"title"` // optional - MimeType string `json:"mime_type"` // optional - FileSize int `json:"file_size"` // optional + FileID string `json:"file_id"` + FileUniqueID string `json:"file_unique_id"` + Duration int `json:"duration"` + Performer string `json:"performer"` // optional + Title string `json:"title"` // optional + MimeType string `json:"mime_type"` // optional + FileSize int `json:"file_size"` // optional } // Document contains information about a document. type Document struct { - FileID string `json:"file_id"` - Thumbnail *PhotoSize `json:"thumb"` // optional - FileName string `json:"file_name"` // optional - MimeType string `json:"mime_type"` // optional - FileSize int `json:"file_size"` // optional + FileID string `json:"file_id"` + FileUniqueID string `json:"file_unique_id"` + Thumbnail *PhotoSize `json:"thumb"` // optional + FileName string `json:"file_name"` // optional + MimeType string `json:"mime_type"` // optional + 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 - IsAnimated bool `json:"is_animated"` // optional + FileID string `json:"file_id"` + FileUniqueID string `json:"file_unique_id"` + Width int `json:"width"` + Height int `json:"height"` + IsAnimated bool `json:"is_animated"` + 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 } -// ChatAnimation contains information about an animation. -type ChatAnimation struct { +// MaskPosition is the position of a mask. +type MaskPosition struct { + Point string `json:"point"` + XShift float32 `json:"x_shift"` + YShift float32 `json:"y_shift"` + Scale float32 `json:"scale"` 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 + Emoji string `json:"emoji"` // optional FileSize int `json:"file_size"` // optional + SetName string `json:"set_name"` // optional } -// Video contains information about a video. -type Video struct { +// 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"` + FileUniqueID string `json:"file_unique_id"` + Width int `json:"width"` + Height int `json:"height"` + Duration int `json:"duration"` + Thumbnail *PhotoSize `json:"thumb"` // optional + MimeType string `json:"mime_type"` // optional + 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 + FileID string `json:"file_id"` + FileUniqueID string `json:"file_unique_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"` - Duration int `json:"duration"` - MimeType string `json:"mime_type"` // optional - FileSize int `json:"file_size"` // optional + FileID string `json:"file_id"` + FileUniqueID string `json:"file_unique_id"` + Duration int `json:"duration"` + MimeType string `json:"mime_type"` // optional + FileSize int `json:"file_size"` // optional } // Contact contains information about a contact.@@ -396,6 +452,7 @@ PhoneNumber string `json:"phone_number"`
FirstName string `json:"first_name"` LastName string `json:"last_name"` // optional UserID int `json:"user_id"` // optional + VCard string `json:"vcard"` // optional } // Location contains information about a place.@@ -412,6 +469,31 @@ Address string `json:"address"`
FoursquareID string `json:"foursquare_id"` // optional } +// PollOption contains information about one answer option in a poll. +type PollOption struct { + Text string `json:"text"` + VoterCount int `json:"voter_count"` +} + +// PollAnswer represents an answer of a user in a non-anonymous poll. +type PollAnswer struct { + PollID string `json:"poll_id"` + User User `json:"user"` + OptionIDs []int `json:"option_ids"` +} + +// Poll contains information about a poll. +type Poll struct { + ID string `json:"id"` + Question string `json:"question"` + Options []PollOption `json:"options"` + IsClosed bool `json:"is_closed"` + IsAnonymous bool `json:"is_anonymous"` + Type string `json:"type"` + AllowsMultipleAnswers bool `json:"allows_multiple_answers"` + CorrectOptionID int `json:"correct_option_id"` // optional +} + // UserProfilePhotos contains a set of user profile photos. type UserProfilePhotos struct { TotalCount int `json:"total_count"`@@ -420,9 +502,10 @@ }
// File contains information about a file to download from Telegram. type File struct { - FileID string `json:"file_id"` - FileSize int `json:"file_size"` // optional - FilePath string `json:"file_path"` // optional + FileID string `json:"file_id"` + FileUniqueID string `json:"file_unique_id"` + FileSize int `json:"file_size"` // optional + FilePath string `json:"file_path"` // optional } // Link returns a full path to the download URL for a File.@@ -442,9 +525,16 @@ }
// KeyboardButton is a button within a custom keyboard. type KeyboardButton struct { - Text string `json:"text"` - RequestContact bool `json:"request_contact"` - RequestLocation bool `json:"request_location"` + Text string `json:"text"` + RequestContact bool `json:"request_contact"` + RequestLocation bool `json:"request_location"` + RequestPoll KeyboardButtonPollType `json:"request_poll"` +} + +// KeyboardButtonPollType represents type of a poll, which is allowed to +// be created and sent when the corresponding button is pressed. +type KeyboardButtonPollType struct { + Type string `json:"type"` } // ReplyKeyboardHide allows the Bot to hide a custom keyboard.@@ -474,11 +564,20 @@ // CallbackGame, if set, MUST be first button in first row.
type InlineKeyboardButton struct { Text string `json:"text"` URL *string `json:"url,omitempty"` // optional + LoginURL *LoginURL `json:"login_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 +} + +// LoginURL is the parameters for the login inline keyboard button type. +type LoginURL struct { + URL string `json:"url"` + ForwardText string `json:"forward_text"` + BotUsername string `json:"bot_username"` + RequestWriteAccess bool `json:"request_write_access"` } // CallbackQuery is data sent when a keyboard button with callback data@@ -504,18 +603,21 @@ // ChatMember is information about a member in a chat.
type ChatMember struct { User *User `json:"user"` Status string `json:"status"` + CustomTitle string `json:"custom_title"` // optional 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 + CanRestrictMembers bool `json:"can_restrict_members,omitempty"` // optional + CanPromoteMembers bool `json:"can_promote_members,omitempty"` // optional + CanChangeInfo bool `json:"can_change_info,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 + IsChatMember bool `json:"is_member"` // optional CanSendMessages bool `json:"can_send_messages,omitempty"` // optional CanSendMediaMessages bool `json:"can_send_media_messages,omitempty"` // optional + CanSendPolls bool `json:"can_send_polls,omitempty"` // optional CanSendOtherMessages bool `json:"can_send_other_messages,omitempty"` // optional CanAddWebPagePreviews bool `json:"can_add_web_page_previews,omitempty"` // optional }@@ -547,11 +649,12 @@ }
// Animation is a GIF animation demonstrating the game. type Animation struct { - FileID string `json:"file_id"` - Thumb PhotoSize `json:"thumb"` - FileName string `json:"file_name"` - MimeType string `json:"mime_type"` - FileSize int `json:"file_size"` + FileID string `json:"file_id"` + FileUniqueID string `json:"file_unique_id"` + Thumb PhotoSize `json:"thumb"` + FileName string `json:"file_name"` + MimeType string `json:"mime_type"` + FileSize int `json:"file_size"` } // GameHighScore is a user's score and position on the leaderboard.@@ -576,27 +679,6 @@
// IsSet returns true if a webhook is currently set. 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.@@ -816,11 +898,27 @@ }
// InlineQueryResultLocation is an inline query response location. type InlineQueryResultLocation 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 + Type string `json:"type"` // required + ID string `json:"id"` // required + Latitude float64 `json:"latitude"` // required + Longitude float64 `json:"longitude"` // required + LivePeriod int `json:"live_period"` // optional + Title string `json:"title"` // required + 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"` +} + +// InlineQueryResultContact is an inline query response contact. +type InlineQueryResultContact struct { + Type string `json:"type"` // required + ID string `json:"id"` // required + PhoneNumber string `json:"phone_number"` // required + FirstName string `json:"first_name"` // required + LastName string `json:"last_name"` + VCard string `json:"vcard"` ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` InputMessageContent interface{} `json:"input_message_content,omitempty"` ThumbURL string `json:"thumb_url"`@@ -893,6 +991,7 @@ type InputContactMessageContent struct {
PhoneNumber string `json:"phone_number"` FirstName string `json:"first_name"` LastName string `json:"last_name"` + VCard string `json:"vcard"` } // Invoice contains basic information about an invoice.@@ -930,9 +1029,9 @@ }
// ShippingOption represents one shipping option. type ShippingOption struct { - ID string `json:"id"` - Title string `json:"title"` - Prices *[]LabeledPrice `json:"prices"` + ID string `json:"id"` + Title string `json:"title"` + Prices []LabeledPrice `json:"prices"` } // SuccessfulPayment contains basic information about a successful payment.@@ -965,6 +1064,58 @@ ShippingOptionID string `json:"shipping_option_id,omitempty"`
OrderInfo *OrderInfo `json:"order_info,omitempty"` } +// StickerSet is a collection of stickers. +type StickerSet struct { + Name string `json:"name"` + Title string `json:"title"` + IsAnimated bool `json:"is_animated"` + ContainsMasks bool `json:"contains_masks"` + Stickers []Sticker `json:"stickers"` +} + +// BaseInputMedia is a base type for the InputMedia types. +type BaseInputMedia struct { + Type string `json:"type"` + Media string `json:"media"` + Caption string `json:"caption"` + ParseMode string `json:"parse_mode"` +} + +// InputMediaPhoto is a photo to send as part of a media group. +type InputMediaPhoto struct { + BaseInputMedia +} + +// InputMediaVideo is a video to send as part of a media group. +type InputMediaVideo struct { + BaseInputMedia + Width int `json:"width"` + Height int `json:"height"` + Duration int `json:"duration"` + SupportsStreaming bool `json:"supports_streaming"` +} + +// InputMediaAnimation is an animation to send as part of a media group. +type InputMediaAnimation struct { + BaseInputMedia + Width int `json:"width"` + Height int `json:"height"` + Duration int `json:"duration"` +} + +// InputMediaAudio is a audio to send as part of a media group. +type InputMediaAudio struct { + BaseInputMedia + Duration int `json:"duration"` + Performer string `json:"performer"` + Title string `json:"title"` +} + +// InputMediaDocument is a audio to send as part of a media group. +type InputMediaDocument struct { + BaseInputMedia +} + // Error is an error containing extra information returned by the Telegram API. type Error struct { Code int@@ -972,6 +1123,7 @@ Message string
ResponseParameters } +// Error message string. func (e Error) Error() string { return e.Message }
@@ -1,14 +1,12 @@
-package tgbotapi_test +package tgbotapi import ( "testing" "time" - - "github.com/go-telegram-bot-api/telegram-bot-api" ) func TestUserStringWith(t *testing.T) { - user := tgbotapi.User{ + user := User{ ID: 0, FirstName: "Test", LastName: "Test",@@ -23,7 +21,7 @@ }
} func TestUserStringWithUserName(t *testing.T) { - user := tgbotapi.User{ + user := User{ ID: 0, FirstName: "Test", LastName: "Test",@@ -37,7 +35,7 @@ }
} func TestMessageTime(t *testing.T) { - message := tgbotapi.Message{Date: 0} + message := Message{Date: 0} date := time.Unix(0, 0) if message.Time() != date {@@ -46,33 +44,33 @@ }
} func TestMessageIsCommandWithCommand(t *testing.T) { - message := tgbotapi.Message{Text: "/command"} - message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}} + message := Message{Text: "/command"} + message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}} - if message.IsCommand() != true { + if !message.IsCommand() { t.Fail() } } func TestIsCommandWithText(t *testing.T) { - message := tgbotapi.Message{Text: "some text"} + message := Message{Text: "some text"} - if message.IsCommand() != false { + if message.IsCommand() { t.Fail() } } func TestIsCommandWithEmptyText(t *testing.T) { - message := tgbotapi.Message{Text: ""} + message := Message{Text: ""} - if message.IsCommand() != false { + if message.IsCommand() { t.Fail() } } func TestCommandWithCommand(t *testing.T) { - message := tgbotapi.Message{Text: "/command"} - message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}} + message := Message{Text: "/command"} + message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}} if message.Command() != "command" { t.Fail()@@ -80,7 +78,7 @@ }
} func TestCommandWithEmptyText(t *testing.T) { - message := tgbotapi.Message{Text: ""} + message := Message{Text: ""} if message.Command() != "" { t.Fail()@@ -88,7 +86,7 @@ }
} func TestCommandWithNonCommand(t *testing.T) { - message := tgbotapi.Message{Text: "test text"} + message := Message{Text: "test text"} if message.Command() != "" { t.Fail()@@ -96,8 +94,8 @@ }
} func TestCommandWithBotName(t *testing.T) { - message := tgbotapi.Message{Text: "/command@testbot"} - message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 16}} + message := Message{Text: "/command@testbot"} + message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 16}} if message.Command() != "command" { t.Fail()@@ -105,8 +103,8 @@ }
} func TestCommandWithAtWithBotName(t *testing.T) { - message := tgbotapi.Message{Text: "/command@testbot"} - message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 16}} + message := Message{Text: "/command@testbot"} + message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 16}} if message.CommandWithAt() != "command@testbot" { t.Fail()@@ -114,37 +112,37 @@ }
} func TestMessageCommandArgumentsWithArguments(t *testing.T) { - message := tgbotapi.Message{Text: "/command with arguments"} - message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}} + message := Message{Text: "/command with arguments"} + message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}} if message.CommandArguments() != "with arguments" { t.Fail() } } func TestMessageCommandArgumentsWithMalformedArguments(t *testing.T) { - message := tgbotapi.Message{Text: "/command-without argument space"} - message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}} + message := Message{Text: "/command-without argument space"} + message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}} if message.CommandArguments() != "without argument space" { t.Fail() } } func TestMessageCommandArgumentsWithoutArguments(t *testing.T) { - message := tgbotapi.Message{Text: "/command"} + message := Message{Text: "/command"} if message.CommandArguments() != "" { t.Fail() } } func TestMessageCommandArgumentsForNonCommand(t *testing.T) { - message := tgbotapi.Message{Text: "test text"} + message := Message{Text: "test text"} if message.CommandArguments() != "" { t.Fail() } } func TestMessageEntityParseURLGood(t *testing.T) { - entity := tgbotapi.MessageEntity{URL: "https://www.google.com"} + entity := MessageEntity{URL: "https://www.google.com"} if _, err := entity.ParseURL(); err != nil { t.Fail()@@ -152,7 +150,7 @@ }
} func TestMessageEntityParseURLBad(t *testing.T) { - entity := tgbotapi.MessageEntity{URL: ""} + entity := MessageEntity{URL: ""} if _, err := entity.ParseURL(); err == nil { t.Fail()@@ -160,31 +158,31 @@ }
} func TestChatIsPrivate(t *testing.T) { - chat := tgbotapi.Chat{ID: 10, Type: "private"} + chat := Chat{ID: 10, Type: "private"} - if chat.IsPrivate() != true { + if !chat.IsPrivate() { t.Fail() } } func TestChatIsGroup(t *testing.T) { - chat := tgbotapi.Chat{ID: 10, Type: "group"} + chat := Chat{ID: 10, Type: "group"} - if chat.IsGroup() != true { + if !chat.IsGroup() { t.Fail() } } func TestChatIsChannel(t *testing.T) { - chat := tgbotapi.Chat{ID: 10, Type: "channel"} + chat := Chat{ID: 10, Type: "channel"} - if chat.IsChannel() != true { + if !chat.IsChannel() { t.Fail() } } func TestChatIsSuperGroup(t *testing.T) { - chat := tgbotapi.Chat{ID: 10, Type: "supergroup"} + chat := Chat{ID: 10, Type: "supergroup"} if !chat.IsSuperGroup() { t.Fail()@@ -272,9 +270,64 @@ }
} func TestFileLink(t *testing.T) { - file := tgbotapi.File{FilePath: "test/test.txt"} + file := File{FilePath: "test/test.txt"} if file.Link("token") != "https://api.telegram.org/file/bottoken/test/test.txt" { t.Fail() } } + +// Ensure all configs are sendable +var ( + _ Chattable = AnimationConfig{} + _ Chattable = AudioConfig{} + _ Chattable = CallbackConfig{} + _ Chattable = ChatAdministratorsConfig{} + _ Chattable = ChatActionConfig{} + _ Chattable = ChatInfoConfig{} + _ Chattable = ChatInviteLinkConfig{} + _ Chattable = ContactConfig{} + _ Chattable = DeleteChatPhotoConfig{} + _ Chattable = DeleteChatStickerSetConfig{} + _ Chattable = DeleteMessageConfig{} + _ Chattable = DocumentConfig{} + _ Chattable = EditMessageCaptionConfig{} + _ Chattable = EditMessageLiveLocationConfig{} + _ Chattable = EditMessageMediaConfig{} + _ Chattable = EditMessageReplyMarkupConfig{} + _ Chattable = EditMessageTextConfig{} + _ Chattable = FileConfig{} + _ Chattable = ForwardConfig{} + _ Chattable = GameConfig{} + _ Chattable = GetChatMemberConfig{} + _ Chattable = GetGameHighScoresConfig{} + _ Chattable = InlineConfig{} + _ Chattable = InvoiceConfig{} + _ Chattable = KickChatMemberConfig{} + _ Chattable = LeaveChatConfig{} + _ Chattable = LocationConfig{} + _ Chattable = MediaGroupConfig{} + _ Chattable = MessageConfig{} + _ Chattable = PhotoConfig{} + _ Chattable = PinChatMessageConfig{} + _ Chattable = PromoteChatMemberConfig{} + _ Chattable = RemoveWebhookConfig{} + _ Chattable = RestrictChatMemberConfig{} + _ Chattable = SendPollConfig{} + _ Chattable = SetChatDescriptionConfig{} + _ Chattable = SetChatPhotoConfig{} + _ Chattable = SetChatTitleConfig{} + _ Chattable = SetGameScoreConfig{} + _ Chattable = StickerConfig{} + _ Chattable = StopPollConfig{} + _ Chattable = StopMessageLiveLocationConfig{} + _ Chattable = UnbanChatMemberConfig{} + _ Chattable = UnpinChatMessageConfig{} + _ Chattable = UpdateConfig{} + _ Chattable = UserProfilePhotosConfig{} + _ Chattable = VenueConfig{} + _ Chattable = VideoConfig{} + _ Chattable = VideoNoteConfig{} + _ Chattable = VoiceConfig{} + _ Chattable = WebhookConfig{} +)