Resolve develop and master conflicts

bot-api-6.1
zhuharev 2020-02-15 16:08:58 +03:00
commit b40fac9202
13 changed files with 1927 additions and 1484 deletions

View File

@ -62,60 +62,7 @@ func main() {
} }
``` ```
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. 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 It's a great place to get started on using keyboards, commands, or other
kinds of reply markup. 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.

622
bot.go
View File

@ -12,7 +12,6 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"strconv"
"strings" "strings"
"time" "time"
@ -67,11 +66,31 @@ func (b *BotAPI) SetAPIEndpoint(apiEndpoint string) {
b.apiEndpoint = apiEndpoint 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. // 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) 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 { if err != nil {
return APIResponse{}, err return APIResponse{}, err
} }
@ -84,15 +103,21 @@ func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse,
} }
if bot.Debug { if bot.Debug {
log.Printf("%s resp: %s", endpoint, bytes) log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes))
} }
if !apiResp.Ok { if !apiResp.Ok {
parameters := ResponseParameters{} var parameters ResponseParameters
if apiResp.Parameters != nil { if apiResp.Parameters != nil {
parameters = *apiResp.Parameters 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 return apiResp, nil
@ -122,21 +147,6 @@ func (bot *BotAPI) decodeAPIResponse(responseBody io.Reader, resp *APIResponse)
return data, nil 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. // UploadFile makes a request to the API with a file.
// //
// Requires the parameter to hold the file not be in the params. // Requires the parameter to hold the file not be in the params.
@ -145,7 +155,7 @@ func (bot *BotAPI) makeMessageRequest(endpoint string, params url.Values) (Messa
// //
// Note that if your FileReader has a size set to -1, it will read // Note that if your FileReader has a size set to -1, it will read
// the file into memory to calculate a size. // 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() ms := multipartstreamer.New()
switch f := file.(type) { switch f := file.(type) {
@ -194,6 +204,10 @@ func (bot *BotAPI) UploadFile(endpoint string, params map[string]string, fieldna
return APIResponse{}, errors.New(ErrBadFileType) 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) method := fmt.Sprintf(bot.apiEndpoint, bot.Token, endpoint)
req, err := http.NewRequest("POST", method, nil) req, err := http.NewRequest("POST", method, nil)
@ -215,7 +229,7 @@ func (bot *BotAPI) UploadFile(endpoint string, params map[string]string, fieldna
} }
if bot.Debug { if bot.Debug {
log.Println(string(bytes)) log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes))
} }
var apiResp APIResponse var apiResp APIResponse
@ -257,11 +271,9 @@ func (bot *BotAPI) GetMe() (User, error) {
} }
var user User var user User
json.Unmarshal(resp.Result, &user) err = json.Unmarshal(resp.Result, &user)
bot.debugLog("getMe", nil, user) return user, err
return user, nil
} }
// IsMessageToMe returns true if message directed to this bot. // 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) return strings.Contains(message.Text, "@"+bot.Self.UserName)
} }
// Send will send a Chattable item to Telegram. // Request sends a Chattable to Telegram, and returns the APIResponse.
// func (bot *BotAPI) Request(c Chattable) (APIResponse, error) {
// It requires the Chattable to send. params, err := c.params()
func (bot *BotAPI) Send(c Chattable) (Message, error) { if err != nil {
switch c.(type) { return APIResponse{}, err
}
switch t := c.(type) {
case Fileable: case Fileable:
return bot.sendFile(c.(Fileable)) if t.useExistingFile() {
return bot.MakeRequest(t.method(), params)
}
return bot.UploadFile(t.method(), params, t.name(), t.getFile())
default: default:
return bot.sendChattable(c) return bot.MakeRequest(c.method(), params)
} }
} }
// debugLog checks if the bot is currently running in debug mode, and if // Send will send a Chattable item to Telegram and provides the
// so will display information about the request and response in the // returned Message.
// debug log. func (bot *BotAPI) Send(c Chattable) (Message, error) {
func (bot *BotAPI) debugLog(context string, v url.Values, message interface{}) { resp, err := bot.Request(c)
if bot.Debug {
log.Printf("%s req : %+v\n", context, v)
log.Printf("%s resp: %+v\n", context, message)
}
}
// 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
}
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)
if err != nil { if err != nil {
return Message{}, err return Message{}, err
} }
var message Message var message Message
json.Unmarshal(resp.Result, &message) err = json.Unmarshal(resp.Result, &message)
bot.debugLog(method, nil, message) return message, err
return message, nil
} }
// sendFile determines if the file is using an existing file or uploading // SendMediaGroup sends a media group and returns the resulting messages.
// a new file, then sends it as needed. func (bot *BotAPI) SendMediaGroup(config MediaGroupConfig) ([]Message, error) {
func (bot *BotAPI) sendFile(config Fileable) (Message, error) { params, _ := config.params()
if config.useExistingFile() {
return bot.sendExisting(config.method(), config)
}
return bot.uploadAndSend(config.method(), config) resp, err := bot.MakeRequest(config.method(), params)
}
// sendChattable sends a Chattable.
func (bot *BotAPI) sendChattable(config Chattable) (Message, error) {
v, err := config.values()
if err != nil { 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 messages, err
return Message{}, err
}
return message, nil
} }
// GetUserProfilePhotos gets a user's profile photos. // GetUserProfilePhotos gets a user's profile photos.
@ -362,46 +336,34 @@ func (bot *BotAPI) sendChattable(config Chattable) (Message, error) {
// It requires UserID. // It requires UserID.
// Offset and Limit are optional. // Offset and Limit are optional.
func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) { func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) {
v := url.Values{} params, _ := config.params()
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))
}
resp, err := bot.MakeRequest("getUserProfilePhotos", v) resp, err := bot.MakeRequest(config.method(), params)
if err != nil { if err != nil {
return UserProfilePhotos{}, err return UserProfilePhotos{}, err
} }
var profilePhotos UserProfilePhotos var profilePhotos UserProfilePhotos
json.Unmarshal(resp.Result, &profilePhotos) err = json.Unmarshal(resp.Result, &profilePhotos)
bot.debugLog("GetUserProfilePhoto", v, profilePhotos) return profilePhotos, err
return profilePhotos, nil
} }
// GetFile returns a File which can download a file from Telegram. // GetFile returns a File which can download a file from Telegram.
// //
// Requires FileID. // Requires FileID.
func (bot *BotAPI) GetFile(config FileConfig) (File, error) { func (bot *BotAPI) GetFile(config FileConfig) (File, error) {
v := url.Values{} params, _ := config.params()
v.Add("file_id", config.FileID)
resp, err := bot.MakeRequest("getFile", v) resp, err := bot.MakeRequest(config.method(), params)
if err != nil { if err != nil {
return File{}, err return File{}, err
} }
var file File var file File
json.Unmarshal(resp.Result, &file) err = json.Unmarshal(resp.Result, &file)
bot.debugLog("GetFile", v, file) return file, err
return file, nil
} }
// GetUpdates fetches updates. // GetUpdates fetches updates.
@ -412,71 +374,23 @@ func (bot *BotAPI) GetFile(config FileConfig) (File, error) {
// Set Timeout to a large number to reduce requests so you can get updates // Set Timeout to a large number to reduce requests so you can get updates
// instantly instead of having to wait between requests. // instantly instead of having to wait between requests.
func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) { func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) {
v := url.Values{} params, _ := config.params()
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))
}
resp, err := bot.MakeRequest("getUpdates", v) resp, err := bot.MakeRequest(config.method(), params)
if err != nil { if err != nil {
return []Update{}, err return []Update{}, err
} }
var updates []Update var updates []Update
json.Unmarshal(resp.Result, &updates) err = json.Unmarshal(resp.Result, &updates)
bot.debugLog("getUpdates", v, updates) return updates, err
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
}
return resp, nil
} }
// GetWebhookInfo allows you to fetch information about a webhook and if // GetWebhookInfo allows you to fetch information about a webhook and if
// one currently is set, along with pending update count and error messages. // one currently is set, along with pending update count and error messages.
func (bot *BotAPI) GetWebhookInfo() (WebhookInfo, error) { func (bot *BotAPI) GetWebhookInfo() (WebhookInfo, error) {
resp, err := bot.MakeRequest("getWebhookInfo", url.Values{}) resp, err := bot.MakeRequest("getWebhookInfo", nil)
if err != nil { if err != nil {
return WebhookInfo{}, err return WebhookInfo{}, err
} }
@ -488,7 +402,7 @@ func (bot *BotAPI) GetWebhookInfo() (WebhookInfo, error) {
} }
// GetUpdatesChan starts and returns a channel for getting updates. // 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) ch := make(chan Update, bot.Buffer)
go func() { go func() {
@ -517,7 +431,7 @@ func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (UpdatesChannel, error) {
} }
}() }()
return ch, nil return ch
} }
// StopReceivingUpdates stops the go routine which receives updates // StopReceivingUpdates stops the go routine which receives updates
@ -545,96 +459,11 @@ func (bot *BotAPI) ListenForWebhook(pattern string) UpdatesChannel {
return ch 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. // GetChat gets information about a chat.
func (bot *BotAPI) GetChat(config ChatConfig) (Chat, error) { func (bot *BotAPI) GetChat(config ChatInfoConfig) (Chat, error) {
v := url.Values{} params, _ := config.params()
if config.SuperGroupUsername == "" { resp, err := bot.MakeRequest(config.method(), params)
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
} else {
v.Add("chat_id", config.SuperGroupUsername)
}
resp, err := bot.MakeRequest("getChat", v)
if err != nil { if err != nil {
return Chat{}, err return Chat{}, err
} }
@ -642,8 +471,6 @@ func (bot *BotAPI) GetChat(config ChatConfig) (Chat, error) {
var chat Chat var chat Chat
err = json.Unmarshal(resp.Result, &chat) err = json.Unmarshal(resp.Result, &chat)
bot.debugLog("getChat", v, chat)
return chat, err return chat, err
} }
@ -651,16 +478,10 @@ func (bot *BotAPI) GetChat(config ChatConfig) (Chat, error) {
// //
// If none have been appointed, only the creator will be returned. // If none have been appointed, only the creator will be returned.
// Bots are not shown, even if they are an administrator. // Bots are not shown, even if they are an administrator.
func (bot *BotAPI) GetChatAdministrators(config ChatConfig) ([]ChatMember, error) { func (bot *BotAPI) GetChatAdministrators(config ChatAdministratorsConfig) ([]ChatMember, error) {
v := url.Values{} params, _ := config.params()
if config.SuperGroupUsername == "" { resp, err := bot.MakeRequest(config.method(), params)
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
} else {
v.Add("chat_id", config.SuperGroupUsername)
}
resp, err := bot.MakeRequest("getChatAdministrators", v)
if err != nil { if err != nil {
return []ChatMember{}, err return []ChatMember{}, err
} }
@ -668,22 +489,14 @@ func (bot *BotAPI) GetChatAdministrators(config ChatConfig) ([]ChatMember, error
var members []ChatMember var members []ChatMember
err = json.Unmarshal(resp.Result, &members) err = json.Unmarshal(resp.Result, &members)
bot.debugLog("getChatAdministrators", v, members)
return members, err return members, err
} }
// GetChatMembersCount gets the number of users in a chat. // GetChatMembersCount gets the number of users in a chat.
func (bot *BotAPI) GetChatMembersCount(config ChatConfig) (int, error) { func (bot *BotAPI) GetChatMembersCount(config ChatMemberCountConfig) (int, error) {
v := url.Values{} params, _ := config.params()
if config.SuperGroupUsername == "" { resp, err := bot.MakeRequest(config.method(), params)
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
} else {
v.Add("chat_id", config.SuperGroupUsername)
}
resp, err := bot.MakeRequest("getChatMembersCount", v)
if err != nil { if err != nil {
return -1, err return -1, err
} }
@ -691,23 +504,14 @@ func (bot *BotAPI) GetChatMembersCount(config ChatConfig) (int, error) {
var count int var count int
err = json.Unmarshal(resp.Result, &count) err = json.Unmarshal(resp.Result, &count)
bot.debugLog("getChatMembersCount", v, count)
return count, err return count, err
} }
// GetChatMember gets a specific chat member. // GetChatMember gets a specific chat member.
func (bot *BotAPI) GetChatMember(config ChatConfigWithUser) (ChatMember, error) { func (bot *BotAPI) GetChatMember(config GetChatMemberConfig) (ChatMember, error) {
v := url.Values{} params, _ := config.params()
if config.SuperGroupUsername == "" { resp, err := bot.MakeRequest(config.method(), params)
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)
if err != nil { if err != nil {
return ChatMember{}, err return ChatMember{}, err
} }
@ -715,115 +519,14 @@ func (bot *BotAPI) GetChatMember(config ChatConfigWithUser) (ChatMember, error)
var member ChatMember var member ChatMember
err = json.Unmarshal(resp.Result, &member) err = json.Unmarshal(resp.Result, &member)
bot.debugLog("getChatMember", v, member)
return member, err 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. // GetGameHighScores allows you to get the high scores for a game.
func (bot *BotAPI) GetGameHighScores(config GetGameHighScoresConfig) ([]GameHighScore, error) { 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 { if err != nil {
return []GameHighScore{}, err return []GameHighScore{}, err
} }
@ -834,65 +537,11 @@ func (bot *BotAPI) GetGameHighScores(config GetGameHighScoresConfig) ([]GameHigh
return highScores, err 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 // GetInviteLink get InviteLink for a chat
func (bot *BotAPI) GetInviteLink(config ChatConfig) (string, error) { func (bot *BotAPI) GetInviteLink(config ChatInviteLinkConfig) (string, error) {
v := url.Values{} params, _ := config.params()
if config.SuperGroupUsername == "" { resp, err := bot.MakeRequest(config.method(), params)
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
} else {
v.Add("chat_id", config.SuperGroupUsername)
}
resp, err := bot.MakeRequest("exportChatInviteLink", v)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -903,74 +552,35 @@ func (bot *BotAPI) GetInviteLink(config ChatConfig) (string, error) {
return inviteLink, err return inviteLink, err
} }
// PinChatMessage pin message in supergroup // GetStickerSet returns a StickerSet.
func (bot *BotAPI) PinChatMessage(config PinChatMessageConfig) (APIResponse, error) { func (bot *BotAPI) GetStickerSet(config GetStickerSetConfig) (StickerSet, error) {
v, err := config.values() params, _ := config.params()
resp, err := bot.MakeRequest(config.method(), params)
if err != nil { 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
} }
// UnpinChatMessage unpin message in supergroup // StopPoll stops a poll and returns the result.
func (bot *BotAPI) UnpinChatMessage(config UnpinChatMessageConfig) (APIResponse, error) { func (bot *BotAPI) StopPoll(config StopPollConfig) (Poll, error) {
v, err := config.values()
if err != nil {
return APIResponse{}, err
}
bot.debugLog(config.method(), v, nil)
return bot.MakeRequest(config.method(), v)
}
// 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) {
params, err := config.params() params, err := config.params()
if err != nil { if err != nil {
return APIResponse{}, err return Poll{}, err
} }
file := config.getFile() resp, err := bot.MakeRequest(config.method(), params)
return bot.UploadFile(config.method(), params, config.name(), file)
}
// DeleteChatPhoto delete photo of chat.
func (bot *BotAPI) DeleteChatPhoto(config DeleteChatPhotoConfig) (APIResponse, error) {
v, err := config.values()
if err != nil { 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
} }

View File

@ -1,14 +1,11 @@
package tgbotapi_test package tgbotapi
import ( import (
"io/ioutil" "io/ioutil"
"log"
"net/http" "net/http"
"os" "os"
"testing" "testing"
"time" "time"
"github.com/go-telegram-bot-api/telegram-bot-api"
) )
const ( const (
@ -25,8 +22,8 @@ const (
ExistingStickerFileID = "BQADAgADcwADjMcoCbdl-6eB--YPAg" ExistingStickerFileID = "BQADAgADcwADjMcoCbdl-6eB--YPAg"
) )
func getBot(t *testing.T) (*tgbotapi.BotAPI, error) { func getBot(t *testing.T) (*BotAPI, error) {
bot, err := tgbotapi.NewBotAPI(TestToken) bot, err := NewBotAPI(TestToken)
bot.Debug = true bot.Debug = true
if err != nil { if err != nil {
@ -38,7 +35,7 @@ func getBot(t *testing.T) (*tgbotapi.BotAPI, error) {
} }
func TestNewBotAPI_notoken(t *testing.T) { func TestNewBotAPI_notoken(t *testing.T) {
_, err := tgbotapi.NewBotAPI("") _, err := NewBotAPI("")
if err == nil { if err == nil {
t.Error(err) t.Error(err)
@ -49,7 +46,7 @@ func TestNewBotAPI_notoken(t *testing.T) {
func TestGetUpdates(t *testing.T) { func TestGetUpdates(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
u := tgbotapi.NewUpdate(0) u := NewUpdate(0)
_, err := bot.GetUpdates(u) _, err := bot.GetUpdates(u)
@ -62,7 +59,7 @@ func TestGetUpdates(t *testing.T) {
func TestSendWithMessage(t *testing.T) { func TestSendWithMessage(t *testing.T) {
bot, _ := getBot(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" msg.ParseMode = "markdown"
_, err := bot.Send(msg) _, err := bot.Send(msg)
@ -75,7 +72,7 @@ func TestSendWithMessage(t *testing.T) {
func TestSendWithMessageReply(t *testing.T) { func TestSendWithMessageReply(t *testing.T) {
bot, _ := getBot(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 msg.ReplyToMessageID = ReplyToMessageID
_, err := bot.Send(msg) _, err := bot.Send(msg)
@ -88,7 +85,7 @@ func TestSendWithMessageReply(t *testing.T) {
func TestSendWithMessageForward(t *testing.T) { func TestSendWithMessageForward(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := tgbotapi.NewForward(ChatID, ChatID, ReplyToMessageID) msg := NewForward(ChatID, ChatID, ReplyToMessageID)
_, err := bot.Send(msg) _, err := bot.Send(msg)
if err != nil { if err != nil {
@ -100,7 +97,7 @@ func TestSendWithMessageForward(t *testing.T) {
func TestSendWithNewPhoto(t *testing.T) { func TestSendWithNewPhoto(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := tgbotapi.NewPhotoUpload(ChatID, "tests/image.jpg") msg := NewPhotoUpload(ChatID, "tests/image.jpg")
msg.Caption = "Test" msg.Caption = "Test"
_, err := bot.Send(msg) _, err := bot.Send(msg)
@ -114,9 +111,9 @@ func TestSendWithNewPhotoWithFileBytes(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
data, _ := ioutil.ReadFile("tests/image.jpg") 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" msg.Caption = "Test"
_, err := bot.Send(msg) _, err := bot.Send(msg)
@ -130,9 +127,9 @@ func TestSendWithNewPhotoWithFileReader(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
f, _ := os.Open("tests/image.jpg") 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" msg.Caption = "Test"
_, err := bot.Send(msg) _, err := bot.Send(msg)
@ -145,7 +142,7 @@ func TestSendWithNewPhotoWithFileReader(t *testing.T) {
func TestSendWithNewPhotoReply(t *testing.T) { func TestSendWithNewPhotoReply(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := tgbotapi.NewPhotoUpload(ChatID, "tests/image.jpg") msg := NewPhotoUpload(ChatID, "tests/image.jpg")
msg.ReplyToMessageID = ReplyToMessageID msg.ReplyToMessageID = ReplyToMessageID
_, err := bot.Send(msg) _, err := bot.Send(msg)
@ -159,7 +156,7 @@ func TestSendWithNewPhotoReply(t *testing.T) {
func TestSendWithExistingPhoto(t *testing.T) { func TestSendWithExistingPhoto(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := tgbotapi.NewPhotoShare(ChatID, ExistingPhotoFileID) msg := NewPhotoShare(ChatID, ExistingPhotoFileID)
msg.Caption = "Test" msg.Caption = "Test"
_, err := bot.Send(msg) _, err := bot.Send(msg)
@ -172,7 +169,7 @@ func TestSendWithExistingPhoto(t *testing.T) {
func TestSendWithNewDocument(t *testing.T) { func TestSendWithNewDocument(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := tgbotapi.NewDocumentUpload(ChatID, "tests/image.jpg") msg := NewDocumentUpload(ChatID, "tests/image.jpg")
_, err := bot.Send(msg) _, err := bot.Send(msg)
if err != nil { if err != nil {
@ -184,7 +181,7 @@ func TestSendWithNewDocument(t *testing.T) {
func TestSendWithExistingDocument(t *testing.T) { func TestSendWithExistingDocument(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := tgbotapi.NewDocumentShare(ChatID, ExistingDocumentFileID) msg := NewDocumentShare(ChatID, ExistingDocumentFileID)
_, err := bot.Send(msg) _, err := bot.Send(msg)
if err != nil { if err != nil {
@ -196,7 +193,7 @@ func TestSendWithExistingDocument(t *testing.T) {
func TestSendWithNewAudio(t *testing.T) { func TestSendWithNewAudio(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := tgbotapi.NewAudioUpload(ChatID, "tests/audio.mp3") msg := NewAudioUpload(ChatID, "tests/audio.mp3")
msg.Title = "TEST" msg.Title = "TEST"
msg.Duration = 10 msg.Duration = 10
msg.Performer = "TEST" msg.Performer = "TEST"
@ -213,7 +210,7 @@ func TestSendWithNewAudio(t *testing.T) {
func TestSendWithExistingAudio(t *testing.T) { func TestSendWithExistingAudio(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := tgbotapi.NewAudioShare(ChatID, ExistingAudioFileID) msg := NewAudioShare(ChatID, ExistingAudioFileID)
msg.Title = "TEST" msg.Title = "TEST"
msg.Duration = 10 msg.Duration = 10
msg.Performer = "TEST" msg.Performer = "TEST"
@ -229,7 +226,7 @@ func TestSendWithExistingAudio(t *testing.T) {
func TestSendWithNewVoice(t *testing.T) { func TestSendWithNewVoice(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := tgbotapi.NewVoiceUpload(ChatID, "tests/voice.ogg") msg := NewVoiceUpload(ChatID, "tests/voice.ogg")
msg.Duration = 10 msg.Duration = 10
_, err := bot.Send(msg) _, err := bot.Send(msg)
@ -242,7 +239,7 @@ func TestSendWithNewVoice(t *testing.T) {
func TestSendWithExistingVoice(t *testing.T) { func TestSendWithExistingVoice(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := tgbotapi.NewVoiceShare(ChatID, ExistingVoiceFileID) msg := NewVoiceShare(ChatID, ExistingVoiceFileID)
msg.Duration = 10 msg.Duration = 10
_, err := bot.Send(msg) _, err := bot.Send(msg)
@ -255,7 +252,7 @@ func TestSendWithExistingVoice(t *testing.T) {
func TestSendWithContact(t *testing.T) { func TestSendWithContact(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
contact := tgbotapi.NewContact(ChatID, "5551234567", "Test") contact := NewContact(ChatID, "5551234567", "Test")
if _, err := bot.Send(contact); err != nil { if _, err := bot.Send(contact); err != nil {
t.Error(err) t.Error(err)
@ -266,7 +263,7 @@ func TestSendWithContact(t *testing.T) {
func TestSendWithLocation(t *testing.T) { func TestSendWithLocation(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
_, err := bot.Send(tgbotapi.NewLocation(ChatID, 40, 40)) _, err := bot.Send(NewLocation(ChatID, 40, 40))
if err != nil { if err != nil {
t.Error(err) t.Error(err)
@ -277,7 +274,7 @@ func TestSendWithLocation(t *testing.T) {
func TestSendWithVenue(t *testing.T) { func TestSendWithVenue(t *testing.T) {
bot, _ := getBot(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 { if _, err := bot.Send(venue); err != nil {
t.Error(err) t.Error(err)
@ -288,7 +285,7 @@ func TestSendWithVenue(t *testing.T) {
func TestSendWithNewVideo(t *testing.T) { func TestSendWithNewVideo(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := tgbotapi.NewVideoUpload(ChatID, "tests/video.mp4") msg := NewVideoUpload(ChatID, "tests/video.mp4")
msg.Duration = 10 msg.Duration = 10
msg.Caption = "TEST" msg.Caption = "TEST"
@ -303,7 +300,7 @@ func TestSendWithNewVideo(t *testing.T) {
func TestSendWithExistingVideo(t *testing.T) { func TestSendWithExistingVideo(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := tgbotapi.NewVideoShare(ChatID, ExistingVideoFileID) msg := NewVideoShare(ChatID, ExistingVideoFileID)
msg.Duration = 10 msg.Duration = 10
msg.Caption = "TEST" msg.Caption = "TEST"
@ -318,7 +315,7 @@ func TestSendWithExistingVideo(t *testing.T) {
func TestSendWithNewVideoNote(t *testing.T) { func TestSendWithNewVideoNote(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := tgbotapi.NewVideoNoteUpload(ChatID, 240, "tests/videonote.mp4") msg := NewVideoNoteUpload(ChatID, 240, "tests/videonote.mp4")
msg.Duration = 10 msg.Duration = 10
_, err := bot.Send(msg) _, err := bot.Send(msg)
@ -332,7 +329,7 @@ func TestSendWithNewVideoNote(t *testing.T) {
func TestSendWithExistingVideoNote(t *testing.T) { func TestSendWithExistingVideoNote(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := tgbotapi.NewVideoNoteShare(ChatID, 240, ExistingVideoNoteFileID) msg := NewVideoNoteShare(ChatID, 240, ExistingVideoNoteFileID)
msg.Duration = 10 msg.Duration = 10
_, err := bot.Send(msg) _, err := bot.Send(msg)
@ -346,7 +343,7 @@ func TestSendWithExistingVideoNote(t *testing.T) {
func TestSendWithNewSticker(t *testing.T) { func TestSendWithNewSticker(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := tgbotapi.NewStickerUpload(ChatID, "tests/image.jpg") msg := NewStickerUpload(ChatID, "tests/image.jpg")
_, err := bot.Send(msg) _, err := bot.Send(msg)
@ -359,7 +356,7 @@ func TestSendWithNewSticker(t *testing.T) {
func TestSendWithExistingSticker(t *testing.T) { func TestSendWithExistingSticker(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := tgbotapi.NewStickerShare(ChatID, ExistingStickerFileID) msg := NewStickerShare(ChatID, ExistingStickerFileID)
_, err := bot.Send(msg) _, err := bot.Send(msg)
@ -372,8 +369,8 @@ func TestSendWithExistingSticker(t *testing.T) {
func TestSendWithNewStickerAndKeyboardHide(t *testing.T) { func TestSendWithNewStickerAndKeyboardHide(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := tgbotapi.NewStickerUpload(ChatID, "tests/image.jpg") msg := NewStickerUpload(ChatID, "tests/image.jpg")
msg.ReplyMarkup = tgbotapi.ReplyKeyboardRemove{ msg.ReplyMarkup = ReplyKeyboardRemove{
RemoveKeyboard: true, RemoveKeyboard: true,
Selective: false, Selective: false,
} }
@ -388,8 +385,8 @@ func TestSendWithNewStickerAndKeyboardHide(t *testing.T) {
func TestSendWithExistingStickerAndKeyboardHide(t *testing.T) { func TestSendWithExistingStickerAndKeyboardHide(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := tgbotapi.NewStickerShare(ChatID, ExistingStickerFileID) msg := NewStickerShare(ChatID, ExistingStickerFileID)
msg.ReplyMarkup = tgbotapi.ReplyKeyboardRemove{ msg.ReplyMarkup = ReplyKeyboardRemove{
RemoveKeyboard: true, RemoveKeyboard: true,
Selective: false, Selective: false,
} }
@ -405,7 +402,9 @@ func TestSendWithExistingStickerAndKeyboardHide(t *testing.T) {
func TestGetFile(t *testing.T) { func TestGetFile(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
file := tgbotapi.FileConfig{FileID: ExistingPhotoFileID} file := FileConfig{
FileID: ExistingPhotoFileID,
}
_, err := bot.GetFile(file) _, err := bot.GetFile(file)
@ -418,7 +417,7 @@ func TestGetFile(t *testing.T) {
func TestSendChatConfig(t *testing.T) { func TestSendChatConfig(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
_, err := bot.Send(tgbotapi.NewChatAction(ChatID, tgbotapi.ChatTyping)) _, err := bot.Request(NewChatAction(ChatID, ChatTyping))
if err != nil { if err != nil {
t.Error(err) t.Error(err)
@ -429,14 +428,14 @@ func TestSendChatConfig(t *testing.T) {
func TestSendEditMessage(t *testing.T) { func TestSendEditMessage(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg, err := bot.Send(tgbotapi.NewMessage(ChatID, "Testing editing.")) msg, err := bot.Send(NewMessage(ChatID, "Testing editing."))
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail() t.Fail()
} }
edit := tgbotapi.EditMessageTextConfig{ edit := EditMessageTextConfig{
BaseEdit: tgbotapi.BaseEdit{ BaseEdit: BaseEdit{
ChatID: ChatID, ChatID: ChatID,
MessageID: msg.MessageID, MessageID: msg.MessageID,
}, },
@ -453,7 +452,7 @@ func TestSendEditMessage(t *testing.T) {
func TestGetUserProfilePhotos(t *testing.T) { func TestGetUserProfilePhotos(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
_, err := bot.GetUserProfilePhotos(tgbotapi.NewUserProfilePhotos(ChatID)) _, err := bot.GetUserProfilePhotos(NewUserProfilePhotos(ChatID))
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail() t.Fail()
@ -465,19 +464,22 @@ func TestSetWebhookWithCert(t *testing.T) {
time.Sleep(time.Second * 2) time.Sleep(time.Second * 2)
bot.RemoveWebhook() bot.Request(RemoveWebhookConfig{})
wh := tgbotapi.NewWebhookWithCert("https://example.com/tgbotapi-test/"+bot.Token, "tests/cert.pem") wh := NewWebhookWithCert("https://example.com/tgbotapi-test/"+bot.Token, "tests/cert.pem")
_, err := bot.SetWebhook(wh) _, err := bot.Request(wh)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail() t.Fail()
} }
_, err = bot.GetWebhookInfo() _, err = bot.GetWebhookInfo()
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
bot.RemoveWebhook()
bot.Request(RemoveWebhookConfig{})
} }
func TestSetWebhookWithoutCert(t *testing.T) { func TestSetWebhookWithoutCert(t *testing.T) {
@ -485,65 +487,65 @@ func TestSetWebhookWithoutCert(t *testing.T) {
time.Sleep(time.Second * 2) time.Sleep(time.Second * 2)
bot.RemoveWebhook() bot.Request(RemoveWebhookConfig{})
wh := tgbotapi.NewWebhook("https://example.com/tgbotapi-test/" + bot.Token) wh := NewWebhook("https://example.com/tgbotapi-test/" + bot.Token)
_, err := bot.SetWebhook(wh) _, err := bot.Request(wh)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail() t.Fail()
} }
info, err := bot.GetWebhookInfo() info, err := bot.GetWebhookInfo()
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
if info.LastErrorDate != 0 { if info.LastErrorDate != 0 {
t.Errorf("[Telegram callback failed]%s", info.LastErrorMessage) t.Errorf("failed to set webhook: %s", info.LastErrorMessage)
} }
bot.RemoveWebhook()
}
func TestUpdatesChan(t *testing.T) { bot.Request(RemoveWebhookConfig{})
bot, _ := getBot(t)
var ucfg tgbotapi.UpdateConfig = tgbotapi.NewUpdate(0)
ucfg.Timeout = 60
_, err := bot.GetUpdatesChan(ucfg)
if err != nil {
t.Error(err)
t.Fail()
}
} }
func TestSendWithMediaGroup(t *testing.T) { func TestSendWithMediaGroup(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
cfg := tgbotapi.NewMediaGroup(ChatID, []interface{}{ cfg := NewMediaGroup(ChatID, []interface{}{
tgbotapi.NewInputMediaPhoto("https://i.imgur.com/unQLJIb.jpg"), NewInputMediaPhoto("https://i.imgur.com/unQLJIb.jpg"),
tgbotapi.NewInputMediaPhoto("https://i.imgur.com/J5qweNZ.jpg"), NewInputMediaPhoto("https://i.imgur.com/J5qweNZ.jpg"),
tgbotapi.NewInputMediaVideo("https://i.imgur.com/F6RmI24.mp4"), NewInputMediaVideo("https://i.imgur.com/F6RmI24.mp4"),
}) })
_, err := bot.Send(cfg)
messages, err := bot.SendMediaGroup(cfg)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
if messages == nil {
t.Error()
}
if len(messages) != 3 {
t.Error()
}
} }
func ExampleNewBotAPI() { func ExampleNewBotAPI() {
bot, err := tgbotapi.NewBotAPI("MyAwesomeBotToken") bot, err := NewBotAPI("MyAwesomeBotToken")
if err != nil { if err != nil {
log.Panic(err) panic(err)
} }
bot.Debug = true bot.Debug = true
log.Printf("Authorized on account %s", bot.Self.UserName) log.Printf("Authorized on account %s", bot.Self.UserName)
u := tgbotapi.NewUpdate(0) u := NewUpdate(0)
u.Timeout = 60 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 // Optional: wait for updates and clear them if you don't want to handle
// a large backlog of old messages // a large backlog of old messages
@ -557,7 +559,7 @@ func ExampleNewBotAPI() {
log.Printf("[%s] %s", update.Message.From.UserName, update.Message.Text) 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 msg.ReplyToMessageID = update.Message.MessageID
bot.Send(msg) bot.Send(msg)
@ -565,26 +567,30 @@ func ExampleNewBotAPI() {
} }
func ExampleNewWebhook() { func ExampleNewWebhook() {
bot, err := tgbotapi.NewBotAPI("MyAwesomeBotToken") bot, err := NewBotAPI("MyAwesomeBotToken")
if err != nil { if err != nil {
log.Fatal(err) panic(err)
} }
bot.Debug = true bot.Debug = true
log.Printf("Authorized on account %s", bot.Self.UserName) 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 { if err != nil {
log.Fatal(err) panic(err)
} }
info, err := bot.GetWebhookInfo() info, err := bot.GetWebhookInfo()
if err != nil { if err != nil {
log.Fatal(err) panic(err)
} }
if info.LastErrorDate != 0 { 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) updates := bot.ListenForWebhook("/" + bot.Token)
go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil) go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil)
@ -593,35 +599,35 @@ func ExampleNewWebhook() {
} }
} }
func ExampleAnswerInlineQuery() { func ExampleInlineConfig() {
bot, err := tgbotapi.NewBotAPI("MyAwesomeBotToken") // create new bot bot, err := NewBotAPI("MyAwesomeBotToken") // create new bot
if err != nil { if err != nil {
log.Panic(err) panic(err)
} }
log.Printf("Authorized on account %s", bot.Self.UserName) log.Printf("Authorized on account %s", bot.Self.UserName)
u := tgbotapi.NewUpdate(0) u := NewUpdate(0)
u.Timeout = 60 u.Timeout = 60
updates, err := bot.GetUpdatesChan(u) updates := bot.GetUpdatesChan(u)
for update := range updates { for update := range updates {
if update.InlineQuery == nil { // if no inline query, ignore it if update.InlineQuery == nil { // if no inline query, ignore it
continue 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 article.Description = update.InlineQuery.Query
inlineConf := tgbotapi.InlineConfig{ inlineConf := InlineConfig{
InlineQueryID: update.InlineQuery.ID, InlineQueryID: update.InlineQuery.ID,
IsPersonal: true, IsPersonal: true,
CacheTime: 0, CacheTime: 0,
Results: []interface{}{article}, Results: []interface{}{article},
} }
if _, err := bot.AnswerInlineQuery(inlineConf); err != nil { if _, err := bot.Request(inlineConf); err != nil {
log.Println(err) log.Println(err)
} }
} }
@ -630,15 +636,15 @@ func ExampleAnswerInlineQuery() {
func TestDeleteMessage(t *testing.T) { func TestDeleteMessage(t *testing.T) {
bot, _ := getBot(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" msg.ParseMode = "markdown"
message, _ := bot.Send(msg) message, _ := bot.Send(msg)
deleteMessageConfig := tgbotapi.DeleteMessageConfig{ deleteMessageConfig := DeleteMessageConfig{
ChatID: message.Chat.ID, ChatID: message.Chat.ID,
MessageID: message.MessageID, MessageID: message.MessageID,
} }
_, err := bot.DeleteMessage(deleteMessageConfig) _, err := bot.Request(deleteMessageConfig)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
@ -649,16 +655,16 @@ func TestDeleteMessage(t *testing.T) {
func TestPinChatMessage(t *testing.T) { func TestPinChatMessage(t *testing.T) {
bot, _ := getBot(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" msg.ParseMode = "markdown"
message, _ := bot.Send(msg) message, _ := bot.Send(msg)
pinChatMessageConfig := tgbotapi.PinChatMessageConfig{ pinChatMessageConfig := PinChatMessageConfig{
ChatID: message.Chat.ID, ChatID: message.Chat.ID,
MessageID: message.MessageID, MessageID: message.MessageID,
DisableNotification: false, DisableNotification: false,
} }
_, err := bot.PinChatMessage(pinChatMessageConfig) _, err := bot.Request(pinChatMessageConfig)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
@ -669,25 +675,61 @@ func TestPinChatMessage(t *testing.T) {
func TestUnpinChatMessage(t *testing.T) { func TestUnpinChatMessage(t *testing.T) {
bot, _ := getBot(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" msg.ParseMode = "markdown"
message, _ := bot.Send(msg) message, _ := bot.Send(msg)
// We need pin message to unpin something // We need pin message to unpin something
pinChatMessageConfig := tgbotapi.PinChatMessageConfig{ pinChatMessageConfig := PinChatMessageConfig{
ChatID: message.Chat.ID, ChatID: message.Chat.ID,
MessageID: message.MessageID, MessageID: message.MessageID,
DisableNotification: false, DisableNotification: false,
} }
_, err := bot.PinChatMessage(pinChatMessageConfig)
unpinChatMessageConfig := tgbotapi.UnpinChatMessageConfig{ if _, err := bot.Request(pinChatMessageConfig); err != nil {
t.Error(err)
t.Fail()
}
unpinChatMessageConfig := UnpinChatMessageConfig{
ChatID: message.Chat.ID, ChatID: message.Chat.ID,
} }
_, err = bot.UnpinChatMessage(unpinChatMessageConfig)
if err != nil { if _, err := bot.Request(unpinChatMessageConfig); err != nil {
t.Error(err) t.Error(err)
t.Fail() 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()
}
}

1551
configs.go

File diff suppressed because it is too large Load Diff

8
go.mod 100644
View File

@ -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

4
go.sum 100644
View File

@ -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=

View File

@ -294,26 +294,58 @@ func NewVoiceShare(chatID int64, fileID string) VoiceConfig {
// two to ten InputMediaPhoto or InputMediaVideo. // two to ten InputMediaPhoto or InputMediaVideo.
func NewMediaGroup(chatID int64, files []interface{}) MediaGroupConfig { func NewMediaGroup(chatID int64, files []interface{}) MediaGroupConfig {
return MediaGroupConfig{ return MediaGroupConfig{
BaseChat: BaseChat{ ChatID: chatID,
ChatID: chatID, Media: files,
},
InputMedia: files,
} }
} }
// NewInputMediaPhoto creates a new InputMediaPhoto. // NewInputMediaPhoto creates a new InputMediaPhoto.
func NewInputMediaPhoto(media string) InputMediaPhoto { func NewInputMediaPhoto(media string) InputMediaPhoto {
return InputMediaPhoto{ return InputMediaPhoto{
Type: "photo", BaseInputMedia{
Media: media, Type: "photo",
Media: media,
},
} }
} }
// NewInputMediaVideo creates a new InputMediaVideo. // NewInputMediaVideo creates a new InputMediaVideo.
func NewInputMediaVideo(media string) InputMediaVideo { func NewInputMediaVideo(media string) InputMediaVideo {
return InputMediaVideo{ return InputMediaVideo{
Type: "video", BaseInputMedia: BaseInputMedia{
Media: media, 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 @@ func NewCallbackWithAlert(id, text string) CallbackConfig {
} }
// NewInvoice creates a new Invoice request to the user. // NewInvoice creates a new Invoice request to the user.
func NewInvoice(chatID int64, title, description, payload, providerToken, startParameter, currency string, prices *[]LabeledPrice) InvoiceConfig { func NewInvoice(chatID int64, title, description, payload, providerToken, startParameter, currency string, prices []LabeledPrice) InvoiceConfig {
return InvoiceConfig{ return InvoiceConfig{
BaseChat: BaseChat{ChatID: chatID}, BaseChat: BaseChat{ChatID: chatID},
Title: title, Title: title,
@ -825,3 +857,60 @@ func NewSetChatPhotoShare(chatID int64, fileID string) SetChatPhotoConfig {
}, },
} }
} }
// 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,
},
}
}

View File

@ -1,47 +1,46 @@
package tgbotapi_test package tgbotapi
import ( import (
"github.com/go-telegram-bot-api/telegram-bot-api"
"testing" "testing"
) )
func TestNewInlineQueryResultArticle(t *testing.T) { func TestNewInlineQueryResultArticle(t *testing.T) {
result := tgbotapi.NewInlineQueryResultArticle("id", "title", "message") result := NewInlineQueryResultArticle("id", "title", "message")
if result.Type != "article" || if result.Type != "article" ||
result.ID != "id" || result.ID != "id" ||
result.Title != "title" || result.Title != "title" ||
result.InputMessageContent.(tgbotapi.InputTextMessageContent).Text != "message" { result.InputMessageContent.(InputTextMessageContent).Text != "message" {
t.Fail() t.Fail()
} }
} }
func TestNewInlineQueryResultArticleMarkdown(t *testing.T) { func TestNewInlineQueryResultArticleMarkdown(t *testing.T) {
result := tgbotapi.NewInlineQueryResultArticleMarkdown("id", "title", "*message*") result := NewInlineQueryResultArticleMarkdown("id", "title", "*message*")
if result.Type != "article" || if result.Type != "article" ||
result.ID != "id" || result.ID != "id" ||
result.Title != "title" || result.Title != "title" ||
result.InputMessageContent.(tgbotapi.InputTextMessageContent).Text != "*message*" || result.InputMessageContent.(InputTextMessageContent).Text != "*message*" ||
result.InputMessageContent.(tgbotapi.InputTextMessageContent).ParseMode != "Markdown" { result.InputMessageContent.(InputTextMessageContent).ParseMode != "Markdown" {
t.Fail() t.Fail()
} }
} }
func TestNewInlineQueryResultArticleHTML(t *testing.T) { func TestNewInlineQueryResultArticleHTML(t *testing.T) {
result := tgbotapi.NewInlineQueryResultArticleHTML("id", "title", "<b>message</b>") result := NewInlineQueryResultArticleHTML("id", "title", "<b>message</b>")
if result.Type != "article" || if result.Type != "article" ||
result.ID != "id" || result.ID != "id" ||
result.Title != "title" || result.Title != "title" ||
result.InputMessageContent.(tgbotapi.InputTextMessageContent).Text != "<b>message</b>" || result.InputMessageContent.(InputTextMessageContent).Text != "<b>message</b>" ||
result.InputMessageContent.(tgbotapi.InputTextMessageContent).ParseMode != "HTML" { result.InputMessageContent.(InputTextMessageContent).ParseMode != "HTML" {
t.Fail() t.Fail()
} }
} }
func TestNewInlineQueryResultGIF(t *testing.T) { func TestNewInlineQueryResultGIF(t *testing.T) {
result := tgbotapi.NewInlineQueryResultGIF("id", "google.com") result := NewInlineQueryResultGIF("id", "google.com")
if result.Type != "gif" || if result.Type != "gif" ||
result.ID != "id" || result.ID != "id" ||
@ -51,7 +50,7 @@ func TestNewInlineQueryResultGIF(t *testing.T) {
} }
func TestNewInlineQueryResultMPEG4GIF(t *testing.T) { func TestNewInlineQueryResultMPEG4GIF(t *testing.T) {
result := tgbotapi.NewInlineQueryResultMPEG4GIF("id", "google.com") result := NewInlineQueryResultMPEG4GIF("id", "google.com")
if result.Type != "mpeg4_gif" || if result.Type != "mpeg4_gif" ||
result.ID != "id" || result.ID != "id" ||
@ -61,7 +60,7 @@ func TestNewInlineQueryResultMPEG4GIF(t *testing.T) {
} }
func TestNewInlineQueryResultPhoto(t *testing.T) { func TestNewInlineQueryResultPhoto(t *testing.T) {
result := tgbotapi.NewInlineQueryResultPhoto("id", "google.com") result := NewInlineQueryResultPhoto("id", "google.com")
if result.Type != "photo" || if result.Type != "photo" ||
result.ID != "id" || result.ID != "id" ||
@ -71,7 +70,7 @@ func TestNewInlineQueryResultPhoto(t *testing.T) {
} }
func TestNewInlineQueryResultPhotoWithThumb(t *testing.T) { func TestNewInlineQueryResultPhotoWithThumb(t *testing.T) {
result := tgbotapi.NewInlineQueryResultPhotoWithThumb("id", "google.com", "thumb.com") result := NewInlineQueryResultPhotoWithThumb("id", "google.com", "thumb.com")
if result.Type != "photo" || if result.Type != "photo" ||
result.ID != "id" || result.ID != "id" ||
@ -82,7 +81,7 @@ func TestNewInlineQueryResultPhotoWithThumb(t *testing.T) {
} }
func TestNewInlineQueryResultVideo(t *testing.T) { func TestNewInlineQueryResultVideo(t *testing.T) {
result := tgbotapi.NewInlineQueryResultVideo("id", "google.com") result := NewInlineQueryResultVideo("id", "google.com")
if result.Type != "video" || if result.Type != "video" ||
result.ID != "id" || result.ID != "id" ||
@ -92,7 +91,7 @@ func TestNewInlineQueryResultVideo(t *testing.T) {
} }
func TestNewInlineQueryResultAudio(t *testing.T) { func TestNewInlineQueryResultAudio(t *testing.T) {
result := tgbotapi.NewInlineQueryResultAudio("id", "google.com", "title") result := NewInlineQueryResultAudio("id", "google.com", "title")
if result.Type != "audio" || if result.Type != "audio" ||
result.ID != "id" || result.ID != "id" ||
@ -103,7 +102,7 @@ func TestNewInlineQueryResultAudio(t *testing.T) {
} }
func TestNewInlineQueryResultVoice(t *testing.T) { func TestNewInlineQueryResultVoice(t *testing.T) {
result := tgbotapi.NewInlineQueryResultVoice("id", "google.com", "title") result := NewInlineQueryResultVoice("id", "google.com", "title")
if result.Type != "voice" || if result.Type != "voice" ||
result.ID != "id" || result.ID != "id" ||
@ -114,7 +113,7 @@ func TestNewInlineQueryResultVoice(t *testing.T) {
} }
func TestNewInlineQueryResultDocument(t *testing.T) { 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" || if result.Type != "document" ||
result.ID != "id" || result.ID != "id" ||
@ -126,7 +125,7 @@ func TestNewInlineQueryResultDocument(t *testing.T) {
} }
func TestNewInlineQueryResultLocation(t *testing.T) { func TestNewInlineQueryResultLocation(t *testing.T) {
result := tgbotapi.NewInlineQueryResultLocation("id", "name", 40, 50) result := NewInlineQueryResultLocation("id", "name", 40, 50)
if result.Type != "location" || if result.Type != "location" ||
result.ID != "id" || result.ID != "id" ||
@ -138,7 +137,7 @@ func TestNewInlineQueryResultLocation(t *testing.T) {
} }
func TestNewEditMessageText(t *testing.T) { func TestNewEditMessageText(t *testing.T) {
edit := tgbotapi.NewEditMessageText(ChatID, ReplyToMessageID, "new text") edit := NewEditMessageText(ChatID, ReplyToMessageID, "new text")
if edit.Text != "new text" || if edit.Text != "new text" ||
edit.BaseEdit.ChatID != ChatID || edit.BaseEdit.ChatID != ChatID ||
@ -148,7 +147,7 @@ func TestNewEditMessageText(t *testing.T) {
} }
func TestNewEditMessageCaption(t *testing.T) { func TestNewEditMessageCaption(t *testing.T) {
edit := tgbotapi.NewEditMessageCaption(ChatID, ReplyToMessageID, "new caption") edit := NewEditMessageCaption(ChatID, ReplyToMessageID, "new caption")
if edit.Caption != "new caption" || if edit.Caption != "new caption" ||
edit.BaseEdit.ChatID != ChatID || edit.BaseEdit.ChatID != ChatID ||
@ -158,15 +157,15 @@ func TestNewEditMessageCaption(t *testing.T) {
} }
func TestNewEditMessageReplyMarkup(t *testing.T) { func TestNewEditMessageReplyMarkup(t *testing.T) {
markup := tgbotapi.InlineKeyboardMarkup{ markup := InlineKeyboardMarkup{
InlineKeyboard: [][]tgbotapi.InlineKeyboardButton{ InlineKeyboard: [][]InlineKeyboardButton{
[]tgbotapi.InlineKeyboardButton{ []InlineKeyboardButton{
tgbotapi.InlineKeyboardButton{Text: "test"}, InlineKeyboardButton{Text: "test"},
}, },
}, },
} }
edit := tgbotapi.NewEditMessageReplyMarkup(ChatID, ReplyToMessageID, markup) edit := NewEditMessageReplyMarkup(ChatID, ReplyToMessageID, markup)
if edit.ReplyMarkup.InlineKeyboard[0][0].Text != "test" || if edit.ReplyMarkup.InlineKeyboard[0][0].Text != "test" ||
edit.BaseEdit.ChatID != ChatID || edit.BaseEdit.ChatID != ChatID ||

97
params.go 100644
View File

@ -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
}

93
params_test.go 100644
View File

@ -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")
}

View File

@ -61,6 +61,8 @@ type (
// Unique identifier for this file // Unique identifier for this file
FileID string `json:"file_id"` FileID string `json:"file_id"`
FileUniqueID string `json:"file_unique_id"`
// File size // File size
FileSize int `json:"file_size"` FileSize int `json:"file_size"`

452
types.go
View File

@ -37,6 +37,8 @@ type Update struct {
CallbackQuery *CallbackQuery `json:"callback_query"` CallbackQuery *CallbackQuery `json:"callback_query"`
ShippingQuery *ShippingQuery `json:"shipping_query"` ShippingQuery *ShippingQuery `json:"shipping_query"`
PreCheckoutQuery *PreCheckoutQuery `json:"pre_checkout_query"` PreCheckoutQuery *PreCheckoutQuery `json:"pre_checkout_query"`
Poll *Poll `json:"poll"`
PollAnswer *PollAnswer `json:"poll_answer"`
} }
// UpdatesChannel is the channel for getting updates. // UpdatesChannel is the channel for getting updates.
@ -51,12 +53,15 @@ func (ch UpdatesChannel) Clear() {
// User is a user on Telegram. // User is a user on Telegram.
type User struct { type User struct {
ID int `json:"id"` ID int `json:"id"`
FirstName string `json:"first_name"` FirstName string `json:"first_name"`
LastName string `json:"last_name"` // optional LastName string `json:"last_name"` // optional
UserName string `json:"username"` // optional UserName string `json:"username"` // optional
LanguageCode string `json:"language_code"` // optional LanguageCode string `json:"language_code"` // optional
IsBot bool `json:"is_bot"` // 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. // String displays a simple text version of a user.
@ -84,23 +89,42 @@ type GroupChat struct {
// ChatPhoto represents a chat photo. // ChatPhoto represents a chat photo.
type ChatPhoto struct { type ChatPhoto struct {
SmallFileID string `json:"small_file_id"` SmallFileID string `json:"small_file_id"`
BigFileID string `json:"big_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. // Chat contains information about the place a message was sent.
type Chat struct { type Chat struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Type string `json:"type"` Type string `json:"type"`
Title string `json:"title"` // optional Title string `json:"title"` // optional
UserName string `json:"username"` // optional UserName string `json:"username"` // optional
FirstName string `json:"first_name"` // optional FirstName string `json:"first_name"` // optional
LastName string `json:"last_name"` // optional LastName string `json:"last_name"` // optional
AllMembersAreAdmins bool `json:"all_members_are_administrators"` // optional AllMembersAreAdmins bool `json:"all_members_are_administrators"` // deprecated, optional
Photo *ChatPhoto `json:"photo"` Photo *ChatPhoto `json:"photo"` // optional
Description string `json:"description,omitempty"` // optional Description string `json:"description,omitempty"` // optional
InviteLink string `json:"invite_link,omitempty"` // optional InviteLink string `json:"invite_link,omitempty"` // optional
PinnedMessage *Message `json:"pinned_message"` // 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. // IsPrivate returns if the Chat is a private conversation.
@ -131,46 +155,53 @@ func (c Chat) ChatConfig() ChatConfig {
// Message is returned by almost every request, and contains data about // Message is returned by almost every request, and contains data about
// almost anything. // almost anything.
type Message struct { type Message struct {
MessageID int `json:"message_id"` MessageID int `json:"message_id"`
From *User `json:"from"` // optional From *User `json:"from"` // optional
Date int `json:"date"` Date int `json:"date"`
Chat *Chat `json:"chat"` Chat *Chat `json:"chat"`
ForwardFrom *User `json:"forward_from"` // optional ForwardFrom *User `json:"forward_from"` // optional
ForwardFromChat *Chat `json:"forward_from_chat"` // optional ForwardFromChat *Chat `json:"forward_from_chat"` // optional
ForwardFromMessageID int `json:"forward_from_message_id"` // optional ForwardFromMessageID int `json:"forward_from_message_id"` // optional
ForwardDate int `json:"forward_date"` // optional ForwardSignature string `json:"forward_signature"` // optional
ReplyToMessage *Message `json:"reply_to_message"` // optional ForwardSenderName string `json:"forward_sender_name"` // optional
EditDate int `json:"edit_date"` // optional ForwardDate int `json:"forward_date"` // optional
Text string `json:"text"` // optional ReplyToMessage *Message `json:"reply_to_message"` // optional
Entities *[]MessageEntity `json:"entities"` // optional EditDate int `json:"edit_date"` // optional
CaptionEntities *[]MessageEntity `json:"caption_entities"` // optional MediaGroupID string `json:"media_group_id"` // optional
Audio *Audio `json:"audio"` // optional AuthorSignature string `json:"author_signature"` // optional
Document *Document `json:"document"` // optional Text string `json:"text"` // optional
Animation *ChatAnimation `json:"animation"` // optional Entities []MessageEntity `json:"entities"` // optional
Game *Game `json:"game"` // optional CaptionEntities []MessageEntity `json:"caption_entities"` // optional
Photo *[]PhotoSize `json:"photo"` // optional Audio *Audio `json:"audio"` // optional
Sticker *Sticker `json:"sticker"` // optional Document *Document `json:"document"` // optional
Video *Video `json:"video"` // optional Animation *ChatAnimation `json:"animation"` // optional
VideoNote *VideoNote `json:"video_note"` // optional Game *Game `json:"game"` // optional
Voice *Voice `json:"voice"` // optional Photo []PhotoSize `json:"photo"` // optional
Caption string `json:"caption"` // optional Sticker *Sticker `json:"sticker"` // optional
Contact *Contact `json:"contact"` // optional Video *Video `json:"video"` // optional
Location *Location `json:"location"` // optional VideoNote *VideoNote `json:"video_note"` // optional
Venue *Venue `json:"venue"` // optional Voice *Voice `json:"voice"` // optional
NewChatMembers *[]User `json:"new_chat_members"` // optional Caption string `json:"caption"` // optional
LeftChatMember *User `json:"left_chat_member"` // optional Contact *Contact `json:"contact"` // optional
NewChatTitle string `json:"new_chat_title"` // optional Location *Location `json:"location"` // optional
NewChatPhoto *[]PhotoSize `json:"new_chat_photo"` // optional Venue *Venue `json:"venue"` // optional
DeleteChatPhoto bool `json:"delete_chat_photo"` // optional Poll *Poll `json:"poll"` // optional
GroupChatCreated bool `json:"group_chat_created"` // optional NewChatMembers []User `json:"new_chat_members"` // optional
SuperGroupChatCreated bool `json:"supergroup_chat_created"` // optional LeftChatMember *User `json:"left_chat_member"` // optional
ChannelChatCreated bool `json:"channel_chat_created"` // optional NewChatTitle string `json:"new_chat_title"` // optional
MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional NewChatPhoto []PhotoSize `json:"new_chat_photo"` // optional
MigrateFromChatID int64 `json:"migrate_from_chat_id"` // optional DeleteChatPhoto bool `json:"delete_chat_photo"` // optional
PinnedMessage *Message `json:"pinned_message"` // optional GroupChatCreated bool `json:"group_chat_created"` // optional
Invoice *Invoice `json:"invoice"` // optional SuperGroupChatCreated bool `json:"supergroup_chat_created"` // optional
SuccessfulPayment *SuccessfulPayment `json:"successful_payment"` // optional ChannelChatCreated bool `json:"channel_chat_created"` // optional
PassportData *PassportData `json:"passport_data,omitempty"` // 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. // Time converts the message timestamp into a Time.
@ -180,11 +211,11 @@ func (m *Message) Time() time.Time {
// IsCommand returns true if message starts with a "bot_command" entity. // IsCommand returns true if message starts with a "bot_command" entity.
func (m *Message) IsCommand() bool { func (m *Message) IsCommand() bool {
if m.Entities == nil || len(*m.Entities) == 0 { if m.Entities == nil || len(m.Entities) == 0 {
return false return false
} }
entity := (*m.Entities)[0] entity := m.Entities[0]
return entity.Offset == 0 && entity.IsCommand() return entity.Offset == 0 && entity.IsCommand()
} }
@ -214,7 +245,7 @@ func (m *Message) CommandWithAt() string {
} }
// IsCommand() checks that the message begins with a bot_command entity // 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] return m.Text[1:entity.Length]
} }
@ -233,7 +264,8 @@ func (m *Message) CommandArguments() string {
} }
// IsCommand() checks that the message begins with a bot_command entity // 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 { if len(m.Text) == entity.Length {
return "" // The command makes up the whole message return "" // The command makes up the whole message
} }
@ -243,11 +275,12 @@ func (m *Message) CommandArguments() string {
// MessageEntity contains information about data in a Message. // MessageEntity contains information about data in a Message.
type MessageEntity struct { type MessageEntity struct {
Type string `json:"type"` Type string `json:"type"`
Offset int `json:"offset"` Offset int `json:"offset"`
Length int `json:"length"` Length int `json:"length"`
URL string `json:"url"` // optional URL string `json:"url"` // optional
User *User `json:"user"` // optional User *User `json:"user"` // optional
Language string `json:"language"` // optional
} }
// ParseURL attempts to parse a URL contained within a MessageEntity. // ParseURL attempts to parse a URL contained within a MessageEntity.
@ -311,41 +344,61 @@ func (e MessageEntity) IsTextLink() bool {
// PhotoSize contains information about photos. // PhotoSize contains information about photos.
type PhotoSize struct { type PhotoSize struct {
FileID string `json:"file_id"` FileID string `json:"file_id"`
Width int `json:"width"` FileUniqueID string `json:"file_unique_id"`
Height int `json:"height"` Width int `json:"width"`
FileSize int `json:"file_size"` // optional Height int `json:"height"`
FileSize int `json:"file_size"` // optional
} }
// Audio contains information about audio. // Audio contains information about audio.
type Audio struct { type Audio struct {
FileID string `json:"file_id"` FileID string `json:"file_id"`
Duration int `json:"duration"` FileUniqueID string `json:"file_unique_id"`
Performer string `json:"performer"` // optional Duration int `json:"duration"`
Title string `json:"title"` // optional Performer string `json:"performer"` // optional
MimeType string `json:"mime_type"` // optional Title string `json:"title"` // optional
FileSize int `json:"file_size"` // optional MimeType string `json:"mime_type"` // optional
FileSize int `json:"file_size"` // optional
} }
// Document contains information about a document. // Document contains information about a document.
type Document struct { type Document struct {
FileID string `json:"file_id"` FileID string `json:"file_id"`
Thumbnail *PhotoSize `json:"thumb"` // optional FileUniqueID string `json:"file_unique_id"`
FileName string `json:"file_name"` // optional Thumbnail *PhotoSize `json:"thumb"` // optional
MimeType string `json:"mime_type"` // optional FileName string `json:"file_name"` // optional
FileSize int `json:"file_size"` // optional MimeType string `json:"mime_type"` // optional
FileSize int `json:"file_size"` // optional
} }
// Sticker contains information about a sticker. // Sticker contains information about a sticker.
type Sticker struct { type Sticker struct {
FileID string `json:"file_id"` FileID string `json:"file_id"`
Width int `json:"width"` FileUniqueID string `json:"file_unique_id"`
Height int `json:"height"` Width int `json:"width"`
Thumbnail *PhotoSize `json:"thumb"` // optional Height int `json:"height"`
Emoji string `json:"emoji"` // optional IsAnimated bool `json:"is_animated"`
FileSize int `json:"file_size"` // optional Thumbnail *PhotoSize `json:"thumb"` // optional
SetName string `json:"set_name"` // optional Emoji string `json:"emoji"` // optional
IsAnimated bool `json:"is_animated"` // optional SetName string `json:"set_name"` // optional
MaskPosition MaskPosition `json:"mask_position"` //optional
FileSize int `json:"file_size"` // optional
}
// 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"`
Thumbnail *PhotoSize `json:"thumb"` // optional
Emoji string `json:"emoji"` // optional
FileSize int `json:"file_size"` // optional
SetName string `json:"set_name"` // optional
} }
// ChatAnimation contains information about an animation. // ChatAnimation contains information about an animation.
@ -362,30 +415,33 @@ type ChatAnimation struct {
// Video contains information about a video. // Video contains information about a video.
type Video struct { type Video struct {
FileID string `json:"file_id"` FileID string `json:"file_id"`
Width int `json:"width"` FileUniqueID string `json:"file_unique_id"`
Height int `json:"height"` Width int `json:"width"`
Duration int `json:"duration"` Height int `json:"height"`
Thumbnail *PhotoSize `json:"thumb"` // optional Duration int `json:"duration"`
MimeType string `json:"mime_type"` // optional Thumbnail *PhotoSize `json:"thumb"` // optional
FileSize int `json:"file_size"` // optional MimeType string `json:"mime_type"` // optional
FileSize int `json:"file_size"` // optional
} }
// VideoNote contains information about a video. // VideoNote contains information about a video.
type VideoNote struct { type VideoNote struct {
FileID string `json:"file_id"` FileID string `json:"file_id"`
Length int `json:"length"` FileUniqueID string `json:"file_unique_id"`
Duration int `json:"duration"` Length int `json:"length"`
Thumbnail *PhotoSize `json:"thumb"` // optional Duration int `json:"duration"`
FileSize int `json:"file_size"` // optional Thumbnail *PhotoSize `json:"thumb"` // optional
FileSize int `json:"file_size"` // optional
} }
// Voice contains information about a voice. // Voice contains information about a voice.
type Voice struct { type Voice struct {
FileID string `json:"file_id"` FileID string `json:"file_id"`
Duration int `json:"duration"` FileUniqueID string `json:"file_unique_id"`
MimeType string `json:"mime_type"` // optional Duration int `json:"duration"`
FileSize int `json:"file_size"` // optional MimeType string `json:"mime_type"` // optional
FileSize int `json:"file_size"` // optional
} }
// Contact contains information about a contact. // Contact contains information about a contact.
@ -396,6 +452,7 @@ type Contact struct {
FirstName string `json:"first_name"` FirstName string `json:"first_name"`
LastName string `json:"last_name"` // optional LastName string `json:"last_name"` // optional
UserID int `json:"user_id"` // optional UserID int `json:"user_id"` // optional
VCard string `json:"vcard"` // optional
} }
// Location contains information about a place. // Location contains information about a place.
@ -412,6 +469,31 @@ type Venue struct {
FoursquareID string `json:"foursquare_id"` // optional 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. // UserProfilePhotos contains a set of user profile photos.
type UserProfilePhotos struct { type UserProfilePhotos struct {
TotalCount int `json:"total_count"` TotalCount int `json:"total_count"`
@ -420,9 +502,10 @@ type UserProfilePhotos struct {
// File contains information about a file to download from Telegram. // File contains information about a file to download from Telegram.
type File struct { type File struct {
FileID string `json:"file_id"` FileID string `json:"file_id"`
FileSize int `json:"file_size"` // optional FileUniqueID string `json:"file_unique_id"`
FilePath string `json:"file_path"` // optional FileSize int `json:"file_size"` // optional
FilePath string `json:"file_path"` // optional
} }
// Link returns a full path to the download URL for a File. // Link returns a full path to the download URL for a File.
@ -442,9 +525,16 @@ type ReplyKeyboardMarkup struct {
// KeyboardButton is a button within a custom keyboard. // KeyboardButton is a button within a custom keyboard.
type KeyboardButton struct { type KeyboardButton struct {
Text string `json:"text"` Text string `json:"text"`
RequestContact bool `json:"request_contact"` RequestContact bool `json:"request_contact"`
RequestLocation bool `json:"request_location"` 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. // ReplyKeyboardHide allows the Bot to hide a custom keyboard.
@ -474,6 +564,7 @@ type InlineKeyboardMarkup struct {
type InlineKeyboardButton struct { type InlineKeyboardButton struct {
Text string `json:"text"` Text string `json:"text"`
URL *string `json:"url,omitempty"` // optional URL *string `json:"url,omitempty"` // optional
LoginURL *LoginURL `json:"login_url,omitempty"` // optional
CallbackData *string `json:"callback_data,omitempty"` // optional CallbackData *string `json:"callback_data,omitempty"` // optional
SwitchInlineQuery *string `json:"switch_inline_query,omitempty"` // optional SwitchInlineQuery *string `json:"switch_inline_query,omitempty"` // optional
SwitchInlineQueryCurrentChat *string `json:"switch_inline_query_current_chat,omitempty"` // optional SwitchInlineQueryCurrentChat *string `json:"switch_inline_query_current_chat,omitempty"` // optional
@ -481,6 +572,14 @@ type InlineKeyboardButton struct {
Pay bool `json:"pay,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 // CallbackQuery is data sent when a keyboard button with callback data
// is clicked. // is clicked.
type CallbackQuery struct { type CallbackQuery struct {
@ -504,18 +603,21 @@ type ForceReply struct {
type ChatMember struct { type ChatMember struct {
User *User `json:"user"` User *User `json:"user"`
Status string `json:"status"` Status string `json:"status"`
CustomTitle string `json:"custom_title"` // optional
UntilDate int64 `json:"until_date,omitempty"` // optional UntilDate int64 `json:"until_date,omitempty"` // optional
CanBeEdited bool `json:"can_be_edited,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 CanPostMessages bool `json:"can_post_messages,omitempty"` // optional
CanEditMessages bool `json:"can_edit_messages,omitempty"` // optional CanEditMessages bool `json:"can_edit_messages,omitempty"` // optional
CanDeleteMessages bool `json:"can_delete_messages,omitempty"` // optional CanDeleteMessages bool `json:"can_delete_messages,omitempty"` // optional
CanInviteUsers bool `json:"can_invite_users,omitempty"` // optional
CanRestrictMembers bool `json:"can_restrict_members,omitempty"` // optional CanRestrictMembers bool `json:"can_restrict_members,omitempty"` // optional
CanPinMessages bool `json:"can_pin_messages,omitempty"` // optional
CanPromoteMembers bool `json:"can_promote_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
CanPinMessages bool `json:"can_pin_messages,omitempty"` // optional
IsChatMember bool `json:"is_member"` // optional
CanSendMessages bool `json:"can_send_messages,omitempty"` // optional CanSendMessages bool `json:"can_send_messages,omitempty"` // optional
CanSendMediaMessages bool `json:"can_send_media_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 CanSendOtherMessages bool `json:"can_send_other_messages,omitempty"` // optional
CanAddWebPagePreviews bool `json:"can_add_web_page_previews,omitempty"` // optional CanAddWebPagePreviews bool `json:"can_add_web_page_previews,omitempty"` // optional
} }
@ -547,11 +649,12 @@ type Game struct {
// Animation is a GIF animation demonstrating the game. // Animation is a GIF animation demonstrating the game.
type Animation struct { type Animation struct {
FileID string `json:"file_id"` FileID string `json:"file_id"`
Thumb PhotoSize `json:"thumb"` FileUniqueID string `json:"file_unique_id"`
FileName string `json:"file_name"` Thumb PhotoSize `json:"thumb"`
MimeType string `json:"mime_type"` FileName string `json:"file_name"`
FileSize int `json:"file_size"` MimeType string `json:"mime_type"`
FileSize int `json:"file_size"`
} }
// GameHighScore is a user's score and position on the leaderboard. // GameHighScore is a user's score and position on the leaderboard.
@ -578,27 +681,6 @@ func (info WebhookInfo) IsSet() bool {
return info.URL != "" 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. // InlineQuery is a Query from Telegram for an inline request.
type InlineQuery struct { type InlineQuery struct {
ID string `json:"id"` ID string `json:"id"`
@ -816,11 +898,27 @@ type InlineQueryResultCachedDocument struct {
// InlineQueryResultLocation is an inline query response location. // InlineQueryResultLocation is an inline query response location.
type InlineQueryResultLocation struct { type InlineQueryResultLocation struct {
Type string `json:"type"` // required Type string `json:"type"` // required
ID string `json:"id"` // required ID string `json:"id"` // required
Latitude float64 `json:"latitude"` // required Latitude float64 `json:"latitude"` // required
Longitude float64 `json:"longitude"` // required Longitude float64 `json:"longitude"` // required
Title string `json:"title"` // 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"` ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
InputMessageContent interface{} `json:"input_message_content,omitempty"` InputMessageContent interface{} `json:"input_message_content,omitempty"`
ThumbURL string `json:"thumb_url"` ThumbURL string `json:"thumb_url"`
@ -893,6 +991,7 @@ type InputContactMessageContent struct {
PhoneNumber string `json:"phone_number"` PhoneNumber string `json:"phone_number"`
FirstName string `json:"first_name"` FirstName string `json:"first_name"`
LastName string `json:"last_name"` LastName string `json:"last_name"`
VCard string `json:"vcard"`
} }
// Invoice contains basic information about an invoice. // Invoice contains basic information about an invoice.
@ -930,9 +1029,9 @@ type OrderInfo struct {
// ShippingOption represents one shipping option. // ShippingOption represents one shipping option.
type ShippingOption struct { type ShippingOption struct {
ID string `json:"id"` ID string `json:"id"`
Title string `json:"title"` Title string `json:"title"`
Prices *[]LabeledPrice `json:"prices"` Prices []LabeledPrice `json:"prices"`
} }
// SuccessfulPayment contains basic information about a successful payment. // SuccessfulPayment contains basic information about a successful payment.
@ -965,6 +1064,58 @@ type PreCheckoutQuery struct {
OrderInfo *OrderInfo `json:"order_info,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. // Error is an error containing extra information returned by the Telegram API.
type Error struct { type Error struct {
Code int Code int
@ -972,6 +1123,7 @@ type Error struct {
ResponseParameters ResponseParameters
} }
// Error message string.
func (e Error) Error() string { func (e Error) Error() string {
return e.Message return e.Message
} }

View File

@ -1,14 +1,12 @@
package tgbotapi_test package tgbotapi
import ( import (
"testing" "testing"
"time" "time"
"github.com/go-telegram-bot-api/telegram-bot-api"
) )
func TestUserStringWith(t *testing.T) { func TestUserStringWith(t *testing.T) {
user := tgbotapi.User{ user := User{
ID: 0, ID: 0,
FirstName: "Test", FirstName: "Test",
LastName: "Test", LastName: "Test",
@ -23,7 +21,7 @@ func TestUserStringWith(t *testing.T) {
} }
func TestUserStringWithUserName(t *testing.T) { func TestUserStringWithUserName(t *testing.T) {
user := tgbotapi.User{ user := User{
ID: 0, ID: 0,
FirstName: "Test", FirstName: "Test",
LastName: "Test", LastName: "Test",
@ -37,7 +35,7 @@ func TestUserStringWithUserName(t *testing.T) {
} }
func TestMessageTime(t *testing.T) { func TestMessageTime(t *testing.T) {
message := tgbotapi.Message{Date: 0} message := Message{Date: 0}
date := time.Unix(0, 0) date := time.Unix(0, 0)
if message.Time() != date { if message.Time() != date {
@ -46,33 +44,33 @@ func TestMessageTime(t *testing.T) {
} }
func TestMessageIsCommandWithCommand(t *testing.T) { func TestMessageIsCommandWithCommand(t *testing.T) {
message := tgbotapi.Message{Text: "/command"} message := Message{Text: "/command"}
message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}} message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
if message.IsCommand() != true { if !message.IsCommand() {
t.Fail() t.Fail()
} }
} }
func TestIsCommandWithText(t *testing.T) { 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() t.Fail()
} }
} }
func TestIsCommandWithEmptyText(t *testing.T) { func TestIsCommandWithEmptyText(t *testing.T) {
message := tgbotapi.Message{Text: ""} message := Message{Text: ""}
if message.IsCommand() != false { if message.IsCommand() {
t.Fail() t.Fail()
} }
} }
func TestCommandWithCommand(t *testing.T) { func TestCommandWithCommand(t *testing.T) {
message := tgbotapi.Message{Text: "/command"} message := Message{Text: "/command"}
message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}} message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
if message.Command() != "command" { if message.Command() != "command" {
t.Fail() t.Fail()
@ -80,7 +78,7 @@ func TestCommandWithCommand(t *testing.T) {
} }
func TestCommandWithEmptyText(t *testing.T) { func TestCommandWithEmptyText(t *testing.T) {
message := tgbotapi.Message{Text: ""} message := Message{Text: ""}
if message.Command() != "" { if message.Command() != "" {
t.Fail() t.Fail()
@ -88,7 +86,7 @@ func TestCommandWithEmptyText(t *testing.T) {
} }
func TestCommandWithNonCommand(t *testing.T) { func TestCommandWithNonCommand(t *testing.T) {
message := tgbotapi.Message{Text: "test text"} message := Message{Text: "test text"}
if message.Command() != "" { if message.Command() != "" {
t.Fail() t.Fail()
@ -96,8 +94,8 @@ func TestCommandWithNonCommand(t *testing.T) {
} }
func TestCommandWithBotName(t *testing.T) { func TestCommandWithBotName(t *testing.T) {
message := tgbotapi.Message{Text: "/command@testbot"} message := Message{Text: "/command@testbot"}
message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 16}} message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 16}}
if message.Command() != "command" { if message.Command() != "command" {
t.Fail() t.Fail()
@ -105,8 +103,8 @@ func TestCommandWithBotName(t *testing.T) {
} }
func TestCommandWithAtWithBotName(t *testing.T) { func TestCommandWithAtWithBotName(t *testing.T) {
message := tgbotapi.Message{Text: "/command@testbot"} message := Message{Text: "/command@testbot"}
message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 16}} message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 16}}
if message.CommandWithAt() != "command@testbot" { if message.CommandWithAt() != "command@testbot" {
t.Fail() t.Fail()
@ -114,37 +112,37 @@ func TestCommandWithAtWithBotName(t *testing.T) {
} }
func TestMessageCommandArgumentsWithArguments(t *testing.T) { func TestMessageCommandArgumentsWithArguments(t *testing.T) {
message := tgbotapi.Message{Text: "/command with arguments"} message := Message{Text: "/command with arguments"}
message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}} message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
if message.CommandArguments() != "with arguments" { if message.CommandArguments() != "with arguments" {
t.Fail() t.Fail()
} }
} }
func TestMessageCommandArgumentsWithMalformedArguments(t *testing.T) { func TestMessageCommandArgumentsWithMalformedArguments(t *testing.T) {
message := tgbotapi.Message{Text: "/command-without argument space"} message := Message{Text: "/command-without argument space"}
message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}} message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
if message.CommandArguments() != "without argument space" { if message.CommandArguments() != "without argument space" {
t.Fail() t.Fail()
} }
} }
func TestMessageCommandArgumentsWithoutArguments(t *testing.T) { func TestMessageCommandArgumentsWithoutArguments(t *testing.T) {
message := tgbotapi.Message{Text: "/command"} message := Message{Text: "/command"}
if message.CommandArguments() != "" { if message.CommandArguments() != "" {
t.Fail() t.Fail()
} }
} }
func TestMessageCommandArgumentsForNonCommand(t *testing.T) { func TestMessageCommandArgumentsForNonCommand(t *testing.T) {
message := tgbotapi.Message{Text: "test text"} message := Message{Text: "test text"}
if message.CommandArguments() != "" { if message.CommandArguments() != "" {
t.Fail() t.Fail()
} }
} }
func TestMessageEntityParseURLGood(t *testing.T) { 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 { if _, err := entity.ParseURL(); err != nil {
t.Fail() t.Fail()
@ -152,7 +150,7 @@ func TestMessageEntityParseURLGood(t *testing.T) {
} }
func TestMessageEntityParseURLBad(t *testing.T) { func TestMessageEntityParseURLBad(t *testing.T) {
entity := tgbotapi.MessageEntity{URL: ""} entity := MessageEntity{URL: ""}
if _, err := entity.ParseURL(); err == nil { if _, err := entity.ParseURL(); err == nil {
t.Fail() t.Fail()
@ -160,31 +158,31 @@ func TestMessageEntityParseURLBad(t *testing.T) {
} }
func TestChatIsPrivate(t *testing.T) { 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() t.Fail()
} }
} }
func TestChatIsGroup(t *testing.T) { 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() t.Fail()
} }
} }
func TestChatIsChannel(t *testing.T) { 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() t.Fail()
} }
} }
func TestChatIsSuperGroup(t *testing.T) { func TestChatIsSuperGroup(t *testing.T) {
chat := tgbotapi.Chat{ID: 10, Type: "supergroup"} chat := Chat{ID: 10, Type: "supergroup"}
if !chat.IsSuperGroup() { if !chat.IsSuperGroup() {
t.Fail() t.Fail()
@ -272,9 +270,64 @@ func TestMessageEntityIsTextLink(t *testing.T) {
} }
func TestFileLink(t *testing.T) { 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" { if file.Link("token") != "https://api.telegram.org/file/bottoken/test/test.txt" {
t.Fail() 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{}
)