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.
It's a great place to get started on using keyboards, commands, or other
kinds of reply markup.
If you need to use webhooks (if you wish to run on Google App Engine),
you may use a slightly different method.
```go
package main
import (
"log"
"net/http"
"github.com/go-telegram-bot-api/telegram-bot-api"
)
func main() {
bot, err := tgbotapi.NewBotAPI("MyAwesomeBotToken")
if err != nil {
log.Fatal(err)
}
bot.Debug = true
log.Printf("Authorized on account %s", bot.Self.UserName)
_, err = bot.SetWebhook(tgbotapi.NewWebhookWithCert("https://www.google.com:8443/"+bot.Token, "cert.pem"))
if err != nil {
log.Fatal(err)
}
info, err := bot.GetWebhookInfo()
if err != nil {
log.Fatal(err)
}
if info.LastErrorDate != 0 {
log.Printf("Telegram callback failed: %s", info.LastErrorMessage)
}
updates := bot.ListenForWebhook("/" + bot.Token)
go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil)
for update := range updates {
log.Printf("%+v\n", update)
}
}
```
If you need, you may generate a self signed certficate, as this requires
HTTPS / TLS. The above example tells Telegram that this is your
certificate and that it should be trusted, even though it is not
properly signed.
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 3560 -subj "//O=Org\CN=Test" -nodes
Now that [Let's Encrypt](https://letsencrypt.org) is available,
you may wish to generate your free TLS certificate there.

622
bot.go
View File

@ -12,7 +12,6 @@ import (
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
@ -67,11 +66,31 @@ func (b *BotAPI) SetAPIEndpoint(apiEndpoint string) {
b.apiEndpoint = apiEndpoint
}
func buildParams(in Params) (out url.Values) {
if in == nil {
return url.Values{}
}
out = url.Values{}
for key, value := range in {
out.Set(key, value)
}
return
}
// MakeRequest makes a request to a specific endpoint with our token.
func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse, error) {
func (bot *BotAPI) MakeRequest(endpoint string, params Params) (APIResponse, error) {
if bot.Debug {
log.Printf("Endpoint: %s, params: %v\n", endpoint, params)
}
method := fmt.Sprintf(bot.apiEndpoint, bot.Token, endpoint)
resp, err := bot.Client.PostForm(method, params)
values := buildParams(params)
resp, err := bot.Client.PostForm(method, values)
if err != nil {
return APIResponse{}, err
}
@ -84,15 +103,21 @@ func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse,
}
if bot.Debug {
log.Printf("%s resp: %s", endpoint, bytes)
log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes))
}
if !apiResp.Ok {
parameters := ResponseParameters{}
var parameters ResponseParameters
if apiResp.Parameters != nil {
parameters = *apiResp.Parameters
}
return apiResp, Error{Code: apiResp.ErrorCode, Message: apiResp.Description, ResponseParameters: parameters}
return apiResp, Error{
Code: apiResp.ErrorCode,
Message: apiResp.Description,
ResponseParameters: parameters,
}
}
return apiResp, nil
@ -122,21 +147,6 @@ func (bot *BotAPI) decodeAPIResponse(responseBody io.Reader, resp *APIResponse)
return data, nil
}
// makeMessageRequest makes a request to a method that returns a Message.
func (bot *BotAPI) makeMessageRequest(endpoint string, params url.Values) (Message, error) {
resp, err := bot.MakeRequest(endpoint, params)
if err != nil {
return Message{}, err
}
var message Message
json.Unmarshal(resp.Result, &message)
bot.debugLog(endpoint, params, message)
return message, nil
}
// UploadFile makes a request to the API with a file.
//
// Requires the parameter to hold the file not be in the params.
@ -145,7 +155,7 @@ func (bot *BotAPI) makeMessageRequest(endpoint string, params url.Values) (Messa
//
// Note that if your FileReader has a size set to -1, it will read
// the file into memory to calculate a size.
func (bot *BotAPI) UploadFile(endpoint string, params map[string]string, fieldname string, file interface{}) (APIResponse, error) {
func (bot *BotAPI) UploadFile(endpoint string, params Params, fieldname string, file interface{}) (APIResponse, error) {
ms := multipartstreamer.New()
switch f := file.(type) {
@ -194,6 +204,10 @@ func (bot *BotAPI) UploadFile(endpoint string, params map[string]string, fieldna
return APIResponse{}, errors.New(ErrBadFileType)
}
if bot.Debug {
log.Printf("Endpoint: %s, fieldname: %s, params: %v, file: %T\n", endpoint, fieldname, params, file)
}
method := fmt.Sprintf(bot.apiEndpoint, bot.Token, endpoint)
req, err := http.NewRequest("POST", method, nil)
@ -215,7 +229,7 @@ func (bot *BotAPI) UploadFile(endpoint string, params map[string]string, fieldna
}
if bot.Debug {
log.Println(string(bytes))
log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes))
}
var apiResp APIResponse
@ -257,11 +271,9 @@ func (bot *BotAPI) GetMe() (User, error) {
}
var user User
json.Unmarshal(resp.Result, &user)
err = json.Unmarshal(resp.Result, &user)
bot.debugLog("getMe", nil, user)
return user, nil
return user, err
}
// IsMessageToMe returns true if message directed to this bot.
@ -271,90 +283,52 @@ func (bot *BotAPI) IsMessageToMe(message Message) bool {
return strings.Contains(message.Text, "@"+bot.Self.UserName)
}
// Send will send a Chattable item to Telegram.
//
// It requires the Chattable to send.
func (bot *BotAPI) Send(c Chattable) (Message, error) {
switch c.(type) {
// Request sends a Chattable to Telegram, and returns the APIResponse.
func (bot *BotAPI) Request(c Chattable) (APIResponse, error) {
params, err := c.params()
if err != nil {
return APIResponse{}, err
}
switch t := c.(type) {
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:
return bot.sendChattable(c)
return bot.MakeRequest(c.method(), params)
}
}
// debugLog checks if the bot is currently running in debug mode, and if
// so will display information about the request and response in the
// debug log.
func (bot *BotAPI) debugLog(context string, v url.Values, message interface{}) {
if bot.Debug {
log.Printf("%s req : %+v\n", context, v)
log.Printf("%s resp: %+v\n", context, message)
}
}
// 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)
// Send will send a Chattable item to Telegram and provides the
// returned Message.
func (bot *BotAPI) Send(c Chattable) (Message, error) {
resp, err := bot.Request(c)
if err != nil {
return Message{}, err
}
var message Message
json.Unmarshal(resp.Result, &message)
err = json.Unmarshal(resp.Result, &message)
bot.debugLog(method, nil, message)
return message, nil
return message, err
}
// sendFile determines if the file is using an existing file or uploading
// a new file, then sends it as needed.
func (bot *BotAPI) sendFile(config Fileable) (Message, error) {
if config.useExistingFile() {
return bot.sendExisting(config.method(), config)
}
// SendMediaGroup sends a media group and returns the resulting messages.
func (bot *BotAPI) SendMediaGroup(config MediaGroupConfig) ([]Message, error) {
params, _ := config.params()
return bot.uploadAndSend(config.method(), config)
}
// sendChattable sends a Chattable.
func (bot *BotAPI) sendChattable(config Chattable) (Message, error) {
v, err := config.values()
resp, err := bot.MakeRequest(config.method(), params)
if err != nil {
return Message{}, err
return nil, err
}
message, err := bot.makeMessageRequest(config.method(), v)
var messages []Message
err = json.Unmarshal(resp.Result, &messages)
if err != nil {
return Message{}, err
}
return message, nil
return messages, err
}
// GetUserProfilePhotos gets a user's profile photos.
@ -362,46 +336,34 @@ func (bot *BotAPI) sendChattable(config Chattable) (Message, error) {
// It requires UserID.
// Offset and Limit are optional.
func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) {
v := url.Values{}
v.Add("user_id", strconv.Itoa(config.UserID))
if config.Offset != 0 {
v.Add("offset", strconv.Itoa(config.Offset))
}
if config.Limit != 0 {
v.Add("limit", strconv.Itoa(config.Limit))
}
params, _ := config.params()
resp, err := bot.MakeRequest("getUserProfilePhotos", v)
resp, err := bot.MakeRequest(config.method(), params)
if err != nil {
return UserProfilePhotos{}, err
}
var profilePhotos UserProfilePhotos
json.Unmarshal(resp.Result, &profilePhotos)
err = json.Unmarshal(resp.Result, &profilePhotos)
bot.debugLog("GetUserProfilePhoto", v, profilePhotos)
return profilePhotos, nil
return profilePhotos, err
}
// GetFile returns a File which can download a file from Telegram.
//
// Requires FileID.
func (bot *BotAPI) GetFile(config FileConfig) (File, error) {
v := url.Values{}
v.Add("file_id", config.FileID)
params, _ := config.params()
resp, err := bot.MakeRequest("getFile", v)
resp, err := bot.MakeRequest(config.method(), params)
if err != nil {
return File{}, err
}
var file File
json.Unmarshal(resp.Result, &file)
err = json.Unmarshal(resp.Result, &file)
bot.debugLog("GetFile", v, file)
return file, nil
return file, err
}
// GetUpdates fetches updates.
@ -412,71 +374,23 @@ func (bot *BotAPI) GetFile(config FileConfig) (File, error) {
// Set Timeout to a large number to reduce requests so you can get updates
// instantly instead of having to wait between requests.
func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) {
v := url.Values{}
if config.Offset != 0 {
v.Add("offset", strconv.Itoa(config.Offset))
}
if config.Limit > 0 {
v.Add("limit", strconv.Itoa(config.Limit))
}
if config.Timeout > 0 {
v.Add("timeout", strconv.Itoa(config.Timeout))
}
params, _ := config.params()
resp, err := bot.MakeRequest("getUpdates", v)
resp, err := bot.MakeRequest(config.method(), params)
if err != nil {
return []Update{}, err
}
var updates []Update
json.Unmarshal(resp.Result, &updates)
err = json.Unmarshal(resp.Result, &updates)
bot.debugLog("getUpdates", v, updates)
return updates, nil
}
// RemoveWebhook unsets the webhook.
func (bot *BotAPI) RemoveWebhook() (APIResponse, error) {
return bot.MakeRequest("setWebhook", url.Values{})
}
// SetWebhook sets a webhook.
//
// If this is set, GetUpdates will not get any data!
//
// If you do not have a legitimate TLS certificate, you need to include
// your self signed certificate with the config.
func (bot *BotAPI) SetWebhook(config WebhookConfig) (APIResponse, error) {
if config.Certificate == nil {
v := url.Values{}
v.Add("url", config.URL.String())
if config.MaxConnections != 0 {
v.Add("max_connections", strconv.Itoa(config.MaxConnections))
}
return bot.MakeRequest("setWebhook", v)
}
params := make(map[string]string)
params["url"] = config.URL.String()
if config.MaxConnections != 0 {
params["max_connections"] = strconv.Itoa(config.MaxConnections)
}
resp, err := bot.UploadFile("setWebhook", params, "certificate", config.Certificate)
if err != nil {
return APIResponse{}, err
}
return resp, nil
return updates, err
}
// GetWebhookInfo allows you to fetch information about a webhook and if
// one currently is set, along with pending update count and error messages.
func (bot *BotAPI) GetWebhookInfo() (WebhookInfo, error) {
resp, err := bot.MakeRequest("getWebhookInfo", url.Values{})
resp, err := bot.MakeRequest("getWebhookInfo", nil)
if err != nil {
return WebhookInfo{}, err
}
@ -488,7 +402,7 @@ func (bot *BotAPI) GetWebhookInfo() (WebhookInfo, error) {
}
// GetUpdatesChan starts and returns a channel for getting updates.
func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (UpdatesChannel, error) {
func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) UpdatesChannel {
ch := make(chan Update, bot.Buffer)
go func() {
@ -517,7 +431,7 @@ func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (UpdatesChannel, error) {
}
}()
return ch, nil
return ch
}
// StopReceivingUpdates stops the go routine which receives updates
@ -545,96 +459,11 @@ func (bot *BotAPI) ListenForWebhook(pattern string) UpdatesChannel {
return ch
}
// AnswerInlineQuery sends a response to an inline query.
//
// Note that you must respond to an inline query within 30 seconds.
func (bot *BotAPI) AnswerInlineQuery(config InlineConfig) (APIResponse, error) {
v := url.Values{}
v.Add("inline_query_id", config.InlineQueryID)
v.Add("cache_time", strconv.Itoa(config.CacheTime))
v.Add("is_personal", strconv.FormatBool(config.IsPersonal))
v.Add("next_offset", config.NextOffset)
data, err := json.Marshal(config.Results)
if err != nil {
return APIResponse{}, err
}
v.Add("results", string(data))
v.Add("switch_pm_text", config.SwitchPMText)
v.Add("switch_pm_parameter", config.SwitchPMParameter)
bot.debugLog("answerInlineQuery", v, nil)
return bot.MakeRequest("answerInlineQuery", v)
}
// AnswerCallbackQuery sends a response to an inline query callback.
func (bot *BotAPI) AnswerCallbackQuery(config CallbackConfig) (APIResponse, error) {
v := url.Values{}
v.Add("callback_query_id", config.CallbackQueryID)
if config.Text != "" {
v.Add("text", config.Text)
}
v.Add("show_alert", strconv.FormatBool(config.ShowAlert))
if config.URL != "" {
v.Add("url", config.URL)
}
v.Add("cache_time", strconv.Itoa(config.CacheTime))
bot.debugLog("answerCallbackQuery", v, nil)
return bot.MakeRequest("answerCallbackQuery", v)
}
// KickChatMember kicks a user from a chat. Note that this only will work
// in supergroups, and requires the bot to be an admin. Also note they
// will be unable to rejoin until they are unbanned.
func (bot *BotAPI) KickChatMember(config KickChatMemberConfig) (APIResponse, error) {
v := url.Values{}
if config.SuperGroupUsername == "" {
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
} else {
v.Add("chat_id", config.SuperGroupUsername)
}
v.Add("user_id", strconv.Itoa(config.UserID))
if config.UntilDate != 0 {
v.Add("until_date", strconv.FormatInt(config.UntilDate, 10))
}
bot.debugLog("kickChatMember", v, nil)
return bot.MakeRequest("kickChatMember", v)
}
// LeaveChat makes the bot leave the chat.
func (bot *BotAPI) LeaveChat(config ChatConfig) (APIResponse, error) {
v := url.Values{}
if config.SuperGroupUsername == "" {
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
} else {
v.Add("chat_id", config.SuperGroupUsername)
}
bot.debugLog("leaveChat", v, nil)
return bot.MakeRequest("leaveChat", v)
}
// GetChat gets information about a chat.
func (bot *BotAPI) GetChat(config ChatConfig) (Chat, error) {
v := url.Values{}
func (bot *BotAPI) GetChat(config ChatInfoConfig) (Chat, error) {
params, _ := config.params()
if config.SuperGroupUsername == "" {
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
} else {
v.Add("chat_id", config.SuperGroupUsername)
}
resp, err := bot.MakeRequest("getChat", v)
resp, err := bot.MakeRequest(config.method(), params)
if err != nil {
return Chat{}, err
}
@ -642,8 +471,6 @@ func (bot *BotAPI) GetChat(config ChatConfig) (Chat, error) {
var chat Chat
err = json.Unmarshal(resp.Result, &chat)
bot.debugLog("getChat", v, chat)
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.
// Bots are not shown, even if they are an administrator.
func (bot *BotAPI) GetChatAdministrators(config ChatConfig) ([]ChatMember, error) {
v := url.Values{}
func (bot *BotAPI) GetChatAdministrators(config ChatAdministratorsConfig) ([]ChatMember, error) {
params, _ := config.params()
if config.SuperGroupUsername == "" {
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
} else {
v.Add("chat_id", config.SuperGroupUsername)
}
resp, err := bot.MakeRequest("getChatAdministrators", v)
resp, err := bot.MakeRequest(config.method(), params)
if err != nil {
return []ChatMember{}, err
}
@ -668,22 +489,14 @@ func (bot *BotAPI) GetChatAdministrators(config ChatConfig) ([]ChatMember, error
var members []ChatMember
err = json.Unmarshal(resp.Result, &members)
bot.debugLog("getChatAdministrators", v, members)
return members, err
}
// GetChatMembersCount gets the number of users in a chat.
func (bot *BotAPI) GetChatMembersCount(config ChatConfig) (int, error) {
v := url.Values{}
func (bot *BotAPI) GetChatMembersCount(config ChatMemberCountConfig) (int, error) {
params, _ := config.params()
if config.SuperGroupUsername == "" {
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
} else {
v.Add("chat_id", config.SuperGroupUsername)
}
resp, err := bot.MakeRequest("getChatMembersCount", v)
resp, err := bot.MakeRequest(config.method(), params)
if err != nil {
return -1, err
}
@ -691,23 +504,14 @@ func (bot *BotAPI) GetChatMembersCount(config ChatConfig) (int, error) {
var count int
err = json.Unmarshal(resp.Result, &count)
bot.debugLog("getChatMembersCount", v, count)
return count, err
}
// GetChatMember gets a specific chat member.
func (bot *BotAPI) GetChatMember(config ChatConfigWithUser) (ChatMember, error) {
v := url.Values{}
func (bot *BotAPI) GetChatMember(config GetChatMemberConfig) (ChatMember, error) {
params, _ := config.params()
if config.SuperGroupUsername == "" {
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
} else {
v.Add("chat_id", config.SuperGroupUsername)
}
v.Add("user_id", strconv.Itoa(config.UserID))
resp, err := bot.MakeRequest("getChatMember", v)
resp, err := bot.MakeRequest(config.method(), params)
if err != nil {
return ChatMember{}, err
}
@ -715,115 +519,14 @@ func (bot *BotAPI) GetChatMember(config ChatConfigWithUser) (ChatMember, error)
var member ChatMember
err = json.Unmarshal(resp.Result, &member)
bot.debugLog("getChatMember", v, member)
return member, err
}
// UnbanChatMember unbans a user from a chat. Note that this only will work
// in supergroups and channels, and requires the bot to be an admin.
func (bot *BotAPI) UnbanChatMember(config ChatMemberConfig) (APIResponse, error) {
v := url.Values{}
if config.SuperGroupUsername != "" {
v.Add("chat_id", config.SuperGroupUsername)
} else if config.ChannelUsername != "" {
v.Add("chat_id", config.ChannelUsername)
} else {
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
}
v.Add("user_id", strconv.Itoa(config.UserID))
bot.debugLog("unbanChatMember", v, nil)
return bot.MakeRequest("unbanChatMember", v)
}
// RestrictChatMember to restrict a user in a supergroup. The bot must be an
//administrator in the supergroup for this to work and must have the
//appropriate admin rights. Pass True for all boolean parameters to lift
//restrictions from a user. Returns True on success.
func (bot *BotAPI) RestrictChatMember(config RestrictChatMemberConfig) (APIResponse, error) {
v := url.Values{}
if config.SuperGroupUsername != "" {
v.Add("chat_id", config.SuperGroupUsername)
} else if config.ChannelUsername != "" {
v.Add("chat_id", config.ChannelUsername)
} else {
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
}
v.Add("user_id", strconv.Itoa(config.UserID))
if config.CanSendMessages != nil {
v.Add("can_send_messages", strconv.FormatBool(*config.CanSendMessages))
}
if config.CanSendMediaMessages != nil {
v.Add("can_send_media_messages", strconv.FormatBool(*config.CanSendMediaMessages))
}
if config.CanSendOtherMessages != nil {
v.Add("can_send_other_messages", strconv.FormatBool(*config.CanSendOtherMessages))
}
if config.CanAddWebPagePreviews != nil {
v.Add("can_add_web_page_previews", strconv.FormatBool(*config.CanAddWebPagePreviews))
}
if config.UntilDate != 0 {
v.Add("until_date", strconv.FormatInt(config.UntilDate, 10))
}
bot.debugLog("restrictChatMember", v, nil)
return bot.MakeRequest("restrictChatMember", v)
}
// PromoteChatMember add admin rights to user
func (bot *BotAPI) PromoteChatMember(config PromoteChatMemberConfig) (APIResponse, error) {
v := url.Values{}
if config.SuperGroupUsername != "" {
v.Add("chat_id", config.SuperGroupUsername)
} else if config.ChannelUsername != "" {
v.Add("chat_id", config.ChannelUsername)
} else {
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
}
v.Add("user_id", strconv.Itoa(config.UserID))
if config.CanChangeInfo != nil {
v.Add("can_change_info", strconv.FormatBool(*config.CanChangeInfo))
}
if config.CanPostMessages != nil {
v.Add("can_post_messages", strconv.FormatBool(*config.CanPostMessages))
}
if config.CanEditMessages != nil {
v.Add("can_edit_messages", strconv.FormatBool(*config.CanEditMessages))
}
if config.CanDeleteMessages != nil {
v.Add("can_delete_messages", strconv.FormatBool(*config.CanDeleteMessages))
}
if config.CanInviteUsers != nil {
v.Add("can_invite_users", strconv.FormatBool(*config.CanInviteUsers))
}
if config.CanRestrictMembers != nil {
v.Add("can_restrict_members", strconv.FormatBool(*config.CanRestrictMembers))
}
if config.CanPinMessages != nil {
v.Add("can_pin_messages", strconv.FormatBool(*config.CanPinMessages))
}
if config.CanPromoteMembers != nil {
v.Add("can_promote_members", strconv.FormatBool(*config.CanPromoteMembers))
}
bot.debugLog("promoteChatMember", v, nil)
return bot.MakeRequest("promoteChatMember", v)
}
// GetGameHighScores allows you to get the high scores for a game.
func (bot *BotAPI) GetGameHighScores(config GetGameHighScoresConfig) ([]GameHighScore, error) {
v, _ := config.values()
params, _ := config.params()
resp, err := bot.MakeRequest(config.method(), v)
resp, err := bot.MakeRequest(config.method(), params)
if err != nil {
return []GameHighScore{}, err
}
@ -834,65 +537,11 @@ func (bot *BotAPI) GetGameHighScores(config GetGameHighScoresConfig) ([]GameHigh
return highScores, err
}
// AnswerShippingQuery allows you to reply to Update with shipping_query parameter.
func (bot *BotAPI) AnswerShippingQuery(config ShippingConfig) (APIResponse, error) {
v := url.Values{}
v.Add("shipping_query_id", config.ShippingQueryID)
v.Add("ok", strconv.FormatBool(config.OK))
if config.OK == true {
data, err := json.Marshal(config.ShippingOptions)
if err != nil {
return APIResponse{}, err
}
v.Add("shipping_options", string(data))
} else {
v.Add("error_message", config.ErrorMessage)
}
bot.debugLog("answerShippingQuery", v, nil)
return bot.MakeRequest("answerShippingQuery", v)
}
// AnswerPreCheckoutQuery allows you to reply to Update with pre_checkout_query.
func (bot *BotAPI) AnswerPreCheckoutQuery(config PreCheckoutConfig) (APIResponse, error) {
v := url.Values{}
v.Add("pre_checkout_query_id", config.PreCheckoutQueryID)
v.Add("ok", strconv.FormatBool(config.OK))
if config.OK != true {
v.Add("error", config.ErrorMessage)
}
bot.debugLog("answerPreCheckoutQuery", v, nil)
return bot.MakeRequest("answerPreCheckoutQuery", v)
}
// DeleteMessage deletes a message in a chat
func (bot *BotAPI) DeleteMessage(config DeleteMessageConfig) (APIResponse, error) {
v, err := config.values()
if err != nil {
return APIResponse{}, err
}
bot.debugLog(config.method(), v, nil)
return bot.MakeRequest(config.method(), v)
}
// GetInviteLink get InviteLink for a chat
func (bot *BotAPI) GetInviteLink(config ChatConfig) (string, error) {
v := url.Values{}
func (bot *BotAPI) GetInviteLink(config ChatInviteLinkConfig) (string, error) {
params, _ := config.params()
if config.SuperGroupUsername == "" {
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
} else {
v.Add("chat_id", config.SuperGroupUsername)
}
resp, err := bot.MakeRequest("exportChatInviteLink", v)
resp, err := bot.MakeRequest(config.method(), params)
if err != nil {
return "", err
}
@ -903,74 +552,35 @@ func (bot *BotAPI) GetInviteLink(config ChatConfig) (string, error) {
return inviteLink, err
}
// PinChatMessage pin message in supergroup
func (bot *BotAPI) PinChatMessage(config PinChatMessageConfig) (APIResponse, error) {
v, err := config.values()
// GetStickerSet returns a StickerSet.
func (bot *BotAPI) GetStickerSet(config GetStickerSetConfig) (StickerSet, error) {
params, _ := config.params()
resp, err := bot.MakeRequest(config.method(), params)
if err != nil {
return APIResponse{}, err
return StickerSet{}, err
}
bot.debugLog(config.method(), v, nil)
var stickers StickerSet
err = json.Unmarshal(resp.Result, &stickers)
return bot.MakeRequest(config.method(), v)
return stickers, err
}
// UnpinChatMessage unpin message in supergroup
func (bot *BotAPI) UnpinChatMessage(config UnpinChatMessageConfig) (APIResponse, error) {
v, err := config.values()
if err != nil {
return APIResponse{}, err
}
bot.debugLog(config.method(), v, nil)
return bot.MakeRequest(config.method(), v)
}
// SetChatTitle change title of chat.
func (bot *BotAPI) SetChatTitle(config SetChatTitleConfig) (APIResponse, error) {
v, err := config.values()
if err != nil {
return APIResponse{}, err
}
bot.debugLog(config.method(), v, nil)
return bot.MakeRequest(config.method(), v)
}
// SetChatDescription change description of chat.
func (bot *BotAPI) SetChatDescription(config SetChatDescriptionConfig) (APIResponse, error) {
v, err := config.values()
if err != nil {
return APIResponse{}, err
}
bot.debugLog(config.method(), v, nil)
return bot.MakeRequest(config.method(), v)
}
// SetChatPhoto change photo of chat.
func (bot *BotAPI) SetChatPhoto(config SetChatPhotoConfig) (APIResponse, error) {
// StopPoll stops a poll and returns the result.
func (bot *BotAPI) StopPoll(config StopPollConfig) (Poll, error) {
params, err := config.params()
if err != nil {
return APIResponse{}, err
return Poll{}, err
}
file := config.getFile()
return bot.UploadFile(config.method(), params, config.name(), file)
}
// DeleteChatPhoto delete photo of chat.
func (bot *BotAPI) DeleteChatPhoto(config DeleteChatPhotoConfig) (APIResponse, error) {
v, err := config.values()
resp, err := bot.MakeRequest(config.method(), params)
if err != nil {
return APIResponse{}, err
return Poll{}, err
}
bot.debugLog(config.method(), v, nil)
var poll Poll
err = json.Unmarshal(resp.Result, &poll)
return bot.MakeRequest(config.method(), v)
return poll, err
}

View File

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

1529
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.
func NewMediaGroup(chatID int64, files []interface{}) MediaGroupConfig {
return MediaGroupConfig{
BaseChat: BaseChat{
ChatID: chatID,
},
InputMedia: files,
Media: files,
}
}
// NewInputMediaPhoto creates a new InputMediaPhoto.
func NewInputMediaPhoto(media string) InputMediaPhoto {
return InputMediaPhoto{
BaseInputMedia{
Type: "photo",
Media: media,
},
}
}
// NewInputMediaVideo creates a new InputMediaVideo.
func NewInputMediaVideo(media string) InputMediaVideo {
return InputMediaVideo{
BaseInputMedia: BaseInputMedia{
Type: "video",
Media: media,
},
}
}
// NewInputMediaAnimation creates a new InputMediaAnimation.
func NewInputMediaAnimation(media string) InputMediaAnimation {
return InputMediaAnimation{
BaseInputMedia: BaseInputMedia{
Type: "animation",
Media: media,
},
}
}
// NewInputMediaAudio creates a new InputMediaAudio.
func NewInputMediaAudio(media string) InputMediaAudio {
return InputMediaAudio{
BaseInputMedia: BaseInputMedia{
Type: "audio",
Media: media,
},
}
}
// NewInputMediaDocument creates a new InputMediaDocument.
func NewInputMediaDocument(media string) InputMediaDocument {
return InputMediaDocument{
BaseInputMedia: BaseInputMedia{
Type: "document",
Media: media,
},
}
}
@ -783,7 +815,7 @@ func NewCallbackWithAlert(id, text string) CallbackConfig {
}
// NewInvoice creates a new Invoice request to the user.
func NewInvoice(chatID int64, title, description, payload, providerToken, startParameter, currency string, prices *[]LabeledPrice) InvoiceConfig {
func NewInvoice(chatID int64, title, description, payload, providerToken, startParameter, currency string, prices []LabeledPrice) InvoiceConfig {
return InvoiceConfig{
BaseChat: BaseChat{ChatID: chatID},
Title: title,
@ -825,3 +857,60 @@ 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 (
"github.com/go-telegram-bot-api/telegram-bot-api"
"testing"
)
func TestNewInlineQueryResultArticle(t *testing.T) {
result := tgbotapi.NewInlineQueryResultArticle("id", "title", "message")
result := NewInlineQueryResultArticle("id", "title", "message")
if result.Type != "article" ||
result.ID != "id" ||
result.Title != "title" ||
result.InputMessageContent.(tgbotapi.InputTextMessageContent).Text != "message" {
result.InputMessageContent.(InputTextMessageContent).Text != "message" {
t.Fail()
}
}
func TestNewInlineQueryResultArticleMarkdown(t *testing.T) {
result := tgbotapi.NewInlineQueryResultArticleMarkdown("id", "title", "*message*")
result := NewInlineQueryResultArticleMarkdown("id", "title", "*message*")
if result.Type != "article" ||
result.ID != "id" ||
result.Title != "title" ||
result.InputMessageContent.(tgbotapi.InputTextMessageContent).Text != "*message*" ||
result.InputMessageContent.(tgbotapi.InputTextMessageContent).ParseMode != "Markdown" {
result.InputMessageContent.(InputTextMessageContent).Text != "*message*" ||
result.InputMessageContent.(InputTextMessageContent).ParseMode != "Markdown" {
t.Fail()
}
}
func TestNewInlineQueryResultArticleHTML(t *testing.T) {
result := tgbotapi.NewInlineQueryResultArticleHTML("id", "title", "<b>message</b>")
result := NewInlineQueryResultArticleHTML("id", "title", "<b>message</b>")
if result.Type != "article" ||
result.ID != "id" ||
result.Title != "title" ||
result.InputMessageContent.(tgbotapi.InputTextMessageContent).Text != "<b>message</b>" ||
result.InputMessageContent.(tgbotapi.InputTextMessageContent).ParseMode != "HTML" {
result.InputMessageContent.(InputTextMessageContent).Text != "<b>message</b>" ||
result.InputMessageContent.(InputTextMessageContent).ParseMode != "HTML" {
t.Fail()
}
}
func TestNewInlineQueryResultGIF(t *testing.T) {
result := tgbotapi.NewInlineQueryResultGIF("id", "google.com")
result := NewInlineQueryResultGIF("id", "google.com")
if result.Type != "gif" ||
result.ID != "id" ||
@ -51,7 +50,7 @@ func TestNewInlineQueryResultGIF(t *testing.T) {
}
func TestNewInlineQueryResultMPEG4GIF(t *testing.T) {
result := tgbotapi.NewInlineQueryResultMPEG4GIF("id", "google.com")
result := NewInlineQueryResultMPEG4GIF("id", "google.com")
if result.Type != "mpeg4_gif" ||
result.ID != "id" ||
@ -61,7 +60,7 @@ func TestNewInlineQueryResultMPEG4GIF(t *testing.T) {
}
func TestNewInlineQueryResultPhoto(t *testing.T) {
result := tgbotapi.NewInlineQueryResultPhoto("id", "google.com")
result := NewInlineQueryResultPhoto("id", "google.com")
if result.Type != "photo" ||
result.ID != "id" ||
@ -71,7 +70,7 @@ func TestNewInlineQueryResultPhoto(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" ||
result.ID != "id" ||
@ -82,7 +81,7 @@ func TestNewInlineQueryResultPhotoWithThumb(t *testing.T) {
}
func TestNewInlineQueryResultVideo(t *testing.T) {
result := tgbotapi.NewInlineQueryResultVideo("id", "google.com")
result := NewInlineQueryResultVideo("id", "google.com")
if result.Type != "video" ||
result.ID != "id" ||
@ -92,7 +91,7 @@ func TestNewInlineQueryResultVideo(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" ||
result.ID != "id" ||
@ -103,7 +102,7 @@ func TestNewInlineQueryResultAudio(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" ||
result.ID != "id" ||
@ -114,7 +113,7 @@ func TestNewInlineQueryResultVoice(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" ||
result.ID != "id" ||
@ -126,7 +125,7 @@ func TestNewInlineQueryResultDocument(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" ||
result.ID != "id" ||
@ -138,7 +137,7 @@ func TestNewInlineQueryResultLocation(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" ||
edit.BaseEdit.ChatID != ChatID ||
@ -148,7 +147,7 @@ func TestNewEditMessageText(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" ||
edit.BaseEdit.ChatID != ChatID ||
@ -158,15 +157,15 @@ func TestNewEditMessageCaption(t *testing.T) {
}
func TestNewEditMessageReplyMarkup(t *testing.T) {
markup := tgbotapi.InlineKeyboardMarkup{
InlineKeyboard: [][]tgbotapi.InlineKeyboardButton{
[]tgbotapi.InlineKeyboardButton{
tgbotapi.InlineKeyboardButton{Text: "test"},
markup := InlineKeyboardMarkup{
InlineKeyboard: [][]InlineKeyboardButton{
[]InlineKeyboardButton{
InlineKeyboardButton{Text: "test"},
},
},
}
edit := tgbotapi.NewEditMessageReplyMarkup(ChatID, ReplyToMessageID, markup)
edit := NewEditMessageReplyMarkup(ChatID, ReplyToMessageID, markup)
if edit.ReplyMarkup.InlineKeyboard[0][0].Text != "test" ||
edit.BaseEdit.ChatID != ChatID ||

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
FileID string `json:"file_id"`
FileUniqueID string `json:"file_unique_id"`
// File size
FileSize int `json:"file_size"`

226
types.go
View File

@ -37,6 +37,8 @@ type Update struct {
CallbackQuery *CallbackQuery `json:"callback_query"`
ShippingQuery *ShippingQuery `json:"shipping_query"`
PreCheckoutQuery *PreCheckoutQuery `json:"pre_checkout_query"`
Poll *Poll `json:"poll"`
PollAnswer *PollAnswer `json:"poll_answer"`
}
// UpdatesChannel is the channel for getting updates.
@ -57,6 +59,9 @@ type User struct {
UserName string `json:"username"` // optional
LanguageCode string `json:"language_code"` // optional
IsBot bool `json:"is_bot"` // optional
CanJoinGroups bool `json:"can_join_groups"` // optional
CanReadAllGroupMessages bool `json:"can_read_all_group_messages"` // optional
SupportsInlineQueries bool `json:"supports_inline_queries"` // optional
}
// String displays a simple text version of a user.
@ -85,7 +90,22 @@ type GroupChat struct {
// ChatPhoto represents a chat photo.
type ChatPhoto struct {
SmallFileID string `json:"small_file_id"`
SmallFileUniqueID string `json:"small_file_unique_id"`
BigFileID string `json:"big_file_id"`
BigFileUniqueID string `json:"big_file_unique_id"`
}
// ChatPermissions describes actions that a non-administrator user is
// allowed to take in a chat. All fields are optional.
type ChatPermissions struct {
CanSendMessages bool `json:"can_send_messages"`
CanSendMediaMessages bool `json:"can_send_media_messages"`
CanSendPolls bool `json:"can_send_polls"`
CanSendOtherMessages bool `json:"can_send_other_messages"`
CanAddWebPagePreviews bool `json:"can_add_web_page_previews"`
CanChangeInfo bool `json:"can_change_info"`
CanInviteUsers bool `json:"can_invite_users"`
CanPinMessages bool `json:"can_pin_messages"`
}
// Chat contains information about the place a message was sent.
@ -96,11 +116,15 @@ type Chat struct {
UserName string `json:"username"` // optional
FirstName string `json:"first_name"` // optional
LastName string `json:"last_name"` // optional
AllMembersAreAdmins bool `json:"all_members_are_administrators"` // optional
Photo *ChatPhoto `json:"photo"`
AllMembersAreAdmins bool `json:"all_members_are_administrators"` // deprecated, optional
Photo *ChatPhoto `json:"photo"` // optional
Description string `json:"description,omitempty"` // optional
InviteLink string `json:"invite_link,omitempty"` // optional
PinnedMessage *Message `json:"pinned_message"` // optional
Permissions *ChatPermissions `json:"permissions"` // optional
SlowModeDelay int `json:"slow_mode_delay"` // optional
StickerSetName string `json:"sticker_set_name"` // optional
CanSetStickerSet bool `json:"can_set_sticker_set"` // optional
}
// IsPrivate returns if the Chat is a private conversation.
@ -138,17 +162,21 @@ type Message struct {
ForwardFrom *User `json:"forward_from"` // optional
ForwardFromChat *Chat `json:"forward_from_chat"` // optional
ForwardFromMessageID int `json:"forward_from_message_id"` // optional
ForwardSignature string `json:"forward_signature"` // optional
ForwardSenderName string `json:"forward_sender_name"` // optional
ForwardDate int `json:"forward_date"` // optional
ReplyToMessage *Message `json:"reply_to_message"` // optional
EditDate int `json:"edit_date"` // optional
MediaGroupID string `json:"media_group_id"` // optional
AuthorSignature string `json:"author_signature"` // optional
Text string `json:"text"` // optional
Entities *[]MessageEntity `json:"entities"` // optional
CaptionEntities *[]MessageEntity `json:"caption_entities"` // optional
Entities []MessageEntity `json:"entities"` // optional
CaptionEntities []MessageEntity `json:"caption_entities"` // optional
Audio *Audio `json:"audio"` // optional
Document *Document `json:"document"` // optional
Animation *ChatAnimation `json:"animation"` // optional
Game *Game `json:"game"` // optional
Photo *[]PhotoSize `json:"photo"` // optional
Photo []PhotoSize `json:"photo"` // optional
Sticker *Sticker `json:"sticker"` // optional
Video *Video `json:"video"` // optional
VideoNote *VideoNote `json:"video_note"` // optional
@ -157,10 +185,11 @@ type Message struct {
Contact *Contact `json:"contact"` // optional
Location *Location `json:"location"` // optional
Venue *Venue `json:"venue"` // optional
NewChatMembers *[]User `json:"new_chat_members"` // optional
Poll *Poll `json:"poll"` // optional
NewChatMembers []User `json:"new_chat_members"` // optional
LeftChatMember *User `json:"left_chat_member"` // optional
NewChatTitle string `json:"new_chat_title"` // optional
NewChatPhoto *[]PhotoSize `json:"new_chat_photo"` // optional
NewChatPhoto []PhotoSize `json:"new_chat_photo"` // optional
DeleteChatPhoto bool `json:"delete_chat_photo"` // optional
GroupChatCreated bool `json:"group_chat_created"` // optional
SuperGroupChatCreated bool `json:"supergroup_chat_created"` // optional
@ -170,7 +199,9 @@ type Message struct {
PinnedMessage *Message `json:"pinned_message"` // optional
Invoice *Invoice `json:"invoice"` // optional
SuccessfulPayment *SuccessfulPayment `json:"successful_payment"` // optional
ConnectedWebsite string `json:"connected_website"` // optional
PassportData *PassportData `json:"passport_data,omitempty"` // optional
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup"` // optional
}
// Time converts the message timestamp into a Time.
@ -180,11 +211,11 @@ func (m *Message) Time() time.Time {
// IsCommand returns true if message starts with a "bot_command" entity.
func (m *Message) IsCommand() bool {
if m.Entities == nil || len(*m.Entities) == 0 {
if m.Entities == nil || len(m.Entities) == 0 {
return false
}
entity := (*m.Entities)[0]
entity := m.Entities[0]
return entity.Offset == 0 && entity.IsCommand()
}
@ -214,7 +245,7 @@ func (m *Message) CommandWithAt() string {
}
// IsCommand() checks that the message begins with a bot_command entity
entity := (*m.Entities)[0]
entity := m.Entities[0]
return m.Text[1:entity.Length]
}
@ -233,7 +264,8 @@ func (m *Message) CommandArguments() string {
}
// IsCommand() checks that the message begins with a bot_command entity
entity := (*m.Entities)[0]
entity := m.Entities[0]
if len(m.Text) == entity.Length {
return "" // The command makes up the whole message
}
@ -248,6 +280,7 @@ type MessageEntity struct {
Length int `json:"length"`
URL string `json:"url"` // optional
User *User `json:"user"` // optional
Language string `json:"language"` // optional
}
// ParseURL attempts to parse a URL contained within a MessageEntity.
@ -312,6 +345,7 @@ func (e MessageEntity) IsTextLink() bool {
// PhotoSize contains information about photos.
type PhotoSize struct {
FileID string `json:"file_id"`
FileUniqueID string `json:"file_unique_id"`
Width int `json:"width"`
Height int `json:"height"`
FileSize int `json:"file_size"` // optional
@ -320,6 +354,7 @@ type PhotoSize struct {
// Audio contains information about audio.
type Audio struct {
FileID string `json:"file_id"`
FileUniqueID string `json:"file_unique_id"`
Duration int `json:"duration"`
Performer string `json:"performer"` // optional
Title string `json:"title"` // optional
@ -330,6 +365,7 @@ type Audio struct {
// Document contains information about a document.
type Document struct {
FileID string `json:"file_id"`
FileUniqueID string `json:"file_unique_id"`
Thumbnail *PhotoSize `json:"thumb"` // optional
FileName string `json:"file_name"` // optional
MimeType string `json:"mime_type"` // optional
@ -338,6 +374,24 @@ type Document struct {
// Sticker contains information about a sticker.
type Sticker struct {
FileID string `json:"file_id"`
FileUniqueID string `json:"file_unique_id"`
Width int `json:"width"`
Height int `json:"height"`
IsAnimated bool `json:"is_animated"`
Thumbnail *PhotoSize `json:"thumb"` // optional
Emoji string `json:"emoji"` // optional
SetName string `json:"set_name"` // optional
MaskPosition MaskPosition `json:"mask_position"` //optional
FileSize int `json:"file_size"` // optional
}
// 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"`
@ -345,7 +399,6 @@ type Sticker struct {
Emoji string `json:"emoji"` // optional
FileSize int `json:"file_size"` // optional
SetName string `json:"set_name"` // optional
IsAnimated bool `json:"is_animated"` // optional
}
// ChatAnimation contains information about an animation.
@ -363,6 +416,7 @@ type ChatAnimation struct {
// Video contains information about a video.
type Video struct {
FileID string `json:"file_id"`
FileUniqueID string `json:"file_unique_id"`
Width int `json:"width"`
Height int `json:"height"`
Duration int `json:"duration"`
@ -374,6 +428,7 @@ type Video struct {
// VideoNote contains information about a video.
type VideoNote struct {
FileID string `json:"file_id"`
FileUniqueID string `json:"file_unique_id"`
Length int `json:"length"`
Duration int `json:"duration"`
Thumbnail *PhotoSize `json:"thumb"` // optional
@ -383,6 +438,7 @@ type VideoNote struct {
// Voice contains information about a voice.
type Voice struct {
FileID string `json:"file_id"`
FileUniqueID string `json:"file_unique_id"`
Duration int `json:"duration"`
MimeType string `json:"mime_type"` // optional
FileSize int `json:"file_size"` // optional
@ -396,6 +452,7 @@ type Contact struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"` // optional
UserID int `json:"user_id"` // optional
VCard string `json:"vcard"` // optional
}
// Location contains information about a place.
@ -412,6 +469,31 @@ type Venue struct {
FoursquareID string `json:"foursquare_id"` // optional
}
// PollOption contains information about one answer option in a poll.
type PollOption struct {
Text string `json:"text"`
VoterCount int `json:"voter_count"`
}
// PollAnswer represents an answer of a user in a non-anonymous poll.
type PollAnswer struct {
PollID string `json:"poll_id"`
User User `json:"user"`
OptionIDs []int `json:"option_ids"`
}
// Poll contains information about a poll.
type Poll struct {
ID string `json:"id"`
Question string `json:"question"`
Options []PollOption `json:"options"`
IsClosed bool `json:"is_closed"`
IsAnonymous bool `json:"is_anonymous"`
Type string `json:"type"`
AllowsMultipleAnswers bool `json:"allows_multiple_answers"`
CorrectOptionID int `json:"correct_option_id"` // optional
}
// UserProfilePhotos contains a set of user profile photos.
type UserProfilePhotos struct {
TotalCount int `json:"total_count"`
@ -421,6 +503,7 @@ type UserProfilePhotos struct {
// File contains information about a file to download from Telegram.
type File struct {
FileID string `json:"file_id"`
FileUniqueID string `json:"file_unique_id"`
FileSize int `json:"file_size"` // optional
FilePath string `json:"file_path"` // optional
}
@ -445,6 +528,13 @@ type KeyboardButton struct {
Text string `json:"text"`
RequestContact bool `json:"request_contact"`
RequestLocation bool `json:"request_location"`
RequestPoll KeyboardButtonPollType `json:"request_poll"`
}
// KeyboardButtonPollType represents type of a poll, which is allowed to
// be created and sent when the corresponding button is pressed.
type KeyboardButtonPollType struct {
Type string `json:"type"`
}
// ReplyKeyboardHide allows the Bot to hide a custom keyboard.
@ -474,6 +564,7 @@ type InlineKeyboardMarkup struct {
type InlineKeyboardButton struct {
Text string `json:"text"`
URL *string `json:"url,omitempty"` // optional
LoginURL *LoginURL `json:"login_url,omitempty"` // optional
CallbackData *string `json:"callback_data,omitempty"` // optional
SwitchInlineQuery *string `json:"switch_inline_query,omitempty"` // optional
SwitchInlineQueryCurrentChat *string `json:"switch_inline_query_current_chat,omitempty"` // optional
@ -481,6 +572,14 @@ type InlineKeyboardButton struct {
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
// is clicked.
type CallbackQuery struct {
@ -504,18 +603,21 @@ type ForceReply struct {
type ChatMember struct {
User *User `json:"user"`
Status string `json:"status"`
CustomTitle string `json:"custom_title"` // optional
UntilDate int64 `json:"until_date,omitempty"` // optional
CanBeEdited bool `json:"can_be_edited,omitempty"` // optional
CanChangeInfo bool `json:"can_change_info,omitempty"` // optional
CanPostMessages bool `json:"can_post_messages,omitempty"` // optional
CanEditMessages bool `json:"can_edit_messages,omitempty"` // optional
CanDeleteMessages bool `json:"can_delete_messages,omitempty"` // optional
CanInviteUsers bool `json:"can_invite_users,omitempty"` // optional
CanRestrictMembers bool `json:"can_restrict_members,omitempty"` // optional
CanPinMessages bool `json:"can_pin_messages,omitempty"` // optional
CanPromoteMembers bool `json:"can_promote_members,omitempty"` // optional
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
CanSendMediaMessages bool `json:"can_send_media_messages,omitempty"` // optional
CanSendPolls bool `json:"can_send_polls,omitempty"` // optional
CanSendOtherMessages bool `json:"can_send_other_messages,omitempty"` // optional
CanAddWebPagePreviews bool `json:"can_add_web_page_previews,omitempty"` // optional
}
@ -548,6 +650,7 @@ type Game struct {
// Animation is a GIF animation demonstrating the game.
type Animation struct {
FileID string `json:"file_id"`
FileUniqueID string `json:"file_unique_id"`
Thumb PhotoSize `json:"thumb"`
FileName string `json:"file_name"`
MimeType string `json:"mime_type"`
@ -578,27 +681,6 @@ func (info WebhookInfo) IsSet() bool {
return info.URL != ""
}
// InputMediaPhoto contains a photo for displaying as part of a media group.
type InputMediaPhoto struct {
Type string `json:"type"`
Media string `json:"media"`
Caption string `json:"caption"`
ParseMode string `json:"parse_mode"`
}
// InputMediaVideo contains a video for displaying as part of a media group.
type InputMediaVideo struct {
Type string `json:"type"`
Media string `json:"media"`
// thumb intentionally missing as it is not currently compatible
Caption string `json:"caption"`
ParseMode string `json:"parse_mode"`
Width int `json:"width"`
Height int `json:"height"`
Duration int `json:"duration"`
SupportsStreaming bool `json:"supports_streaming"`
}
// InlineQuery is a Query from Telegram for an inline request.
type InlineQuery struct {
ID string `json:"id"`
@ -820,6 +902,7 @@ type InlineQueryResultLocation struct {
ID string `json:"id"` // required
Latitude float64 `json:"latitude"` // required
Longitude float64 `json:"longitude"` // required
LivePeriod int `json:"live_period"` // optional
Title string `json:"title"` // required
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
InputMessageContent interface{} `json:"input_message_content,omitempty"`
@ -828,6 +911,21 @@ type InlineQueryResultLocation struct {
ThumbHeight int `json:"thumb_height"`
}
// InlineQueryResultContact is an inline query response contact.
type InlineQueryResultContact struct {
Type string `json:"type"` // required
ID string `json:"id"` // required
PhoneNumber string `json:"phone_number"` // required
FirstName string `json:"first_name"` // required
LastName string `json:"last_name"`
VCard string `json:"vcard"`
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
InputMessageContent interface{} `json:"input_message_content,omitempty"`
ThumbURL string `json:"thumb_url"`
ThumbWidth int `json:"thumb_width"`
ThumbHeight int `json:"thumb_height"`
}
// InlineQueryResultVenue is an inline query response venue.
type InlineQueryResultVenue struct {
Type string `json:"type"` // required
@ -893,6 +991,7 @@ type InputContactMessageContent struct {
PhoneNumber string `json:"phone_number"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
VCard string `json:"vcard"`
}
// Invoice contains basic information about an invoice.
@ -932,7 +1031,7 @@ type OrderInfo struct {
type ShippingOption struct {
ID string `json:"id"`
Title string `json:"title"`
Prices *[]LabeledPrice `json:"prices"`
Prices []LabeledPrice `json:"prices"`
}
// SuccessfulPayment contains basic information about a successful payment.
@ -965,6 +1064,58 @@ type PreCheckoutQuery struct {
OrderInfo *OrderInfo `json:"order_info,omitempty"`
}
// StickerSet is a collection of stickers.
type StickerSet struct {
Name string `json:"name"`
Title string `json:"title"`
IsAnimated bool `json:"is_animated"`
ContainsMasks bool `json:"contains_masks"`
Stickers []Sticker `json:"stickers"`
}
// BaseInputMedia is a base type for the InputMedia types.
type BaseInputMedia struct {
Type string `json:"type"`
Media string `json:"media"`
Caption string `json:"caption"`
ParseMode string `json:"parse_mode"`
}
// InputMediaPhoto is a photo to send as part of a media group.
type InputMediaPhoto struct {
BaseInputMedia
}
// InputMediaVideo is a video to send as part of a media group.
type InputMediaVideo struct {
BaseInputMedia
Width int `json:"width"`
Height int `json:"height"`
Duration int `json:"duration"`
SupportsStreaming bool `json:"supports_streaming"`
}
// InputMediaAnimation is an animation to send as part of a media group.
type InputMediaAnimation struct {
BaseInputMedia
Width int `json:"width"`
Height int `json:"height"`
Duration int `json:"duration"`
}
// InputMediaAudio is a audio to send as part of a media group.
type InputMediaAudio struct {
BaseInputMedia
Duration int `json:"duration"`
Performer string `json:"performer"`
Title string `json:"title"`
}
// InputMediaDocument is a audio to send as part of a media group.
type InputMediaDocument struct {
BaseInputMedia
}
// Error is an error containing extra information returned by the Telegram API.
type Error struct {
Code int
@ -972,6 +1123,7 @@ type Error struct {
ResponseParameters
}
// Error message string.
func (e Error) Error() string {
return e.Message
}

View File

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