Compare commits

..

34 Commits

Author SHA1 Message Date
Syfaro ffe77fb717 Updates for Bot API 4.3. 6 years ago
Syfaro db88019230 Add support for Polls and other API 4.2 updates. 6 years ago
Syfaro fa40708257 Add SendMediaGroup method. 7 years ago
Syfaro 5781187bc2 Add go mod files. 7 years ago
Syfaro 290b9363d4 Fix bot_test.go, update README. 7 years ago
Syfaro afda722fc3 Remove unused error returned by GetUpdatesChan. 7 years ago
Syfaro a746f39d22 Move tests into tgbotapi package. 7 years ago
Syfaro cdcb93df5f No reason to use pointers to arrays. 7 years ago
Syfaro 4d758f17d4 Add some missing fields, generalize configs, remove unneeded methods. 7 years ago
Syfaro 1f859674f7 Consistency of variable names. 7 years ago
Syfaro 655c3a4137 Remove outdated and old comments. 7 years ago
Syfaro 03815bf5bd Unify params and values, introduce helpers to deal with fields. 7 years ago
Syfaro 9d2d117c0e Merge branch 'master' into develop 7 years ago
Syfaro 23ed97b145 Merge master into develop. 7 years ago
Syfaro b728fa78fc More various small code quality improvements. 7 years ago
Syfaro 1aef8c8c45 Remove new methods that returned APIResponse. 7 years ago
Syfaro 6a6de7e674 Merge master into develop. 7 years ago
Syfaro 9653a4aad4 Handle some ignored errors. 8 years ago
Syfaro c268ddc5b9 Fix comments that were copied. 8 years ago
Syfaro bb07769ea9 Bot API 3.5. 8 years ago
Syfaro e840fa3b0f Finish Bot API 3.4. 8 years ago
Syfaro 72f87b43e3 Fix backwards compatibility for Live Location. 8 years ago
Syfaro 4ec899a62e Merge remote-tracking branch 'pr0head/master' into develop 8 years ago
Syfaro 0a654beca4 Move debug output into a single location. 8 years ago
Syfaro 8b7b15afc2 Update README.md. 8 years ago
Syfaro ef374648bf Bot API 3.3. 8 years ago
Syfaro ac87082c55 Remove remaining methods that returned an APIResponse. 8 years ago
Syfaro 95a923dc4c Update tests. 8 years ago
Syfaro 271adc4d97 Bot API 3.2, introduce Request to replace APIResponse methods. 8 years ago
Syfaro e97c2417c9 Resolve some linter issues. 8 years ago
pr0head dffc002f9e Fix struct for InlineQueryResultLocation 8 years ago
pr0head 5cbecde819 Added `editMessageLiveLocation` and `stopMessageLiveLocation` methods 8 years ago
pr0head 7031d820be Added `live_period` for Location (tests) 8 years ago
pr0head 65947daaab Added `live_period` for Location 8 years ago
  1. 57
      README.md
  2. 629
      bot.go
  3. 238
      bot_test.go
  4. 1463
      configs.go
  5. 4
      go.mod
  6. 106
      helpers.go
  7. 52
      helpers_test.go
  8. 100
      params.go
  9. 252
      types.go
  10. 113
      types_test.go

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

629
bot.go

@ -12,7 +12,6 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"strconv"
"strings" "strings"
"time" "time"
@ -25,8 +24,8 @@ type BotAPI struct {
Debug bool `json:"debug"` Debug bool `json:"debug"`
Buffer int `json:"buffer"` Buffer int `json:"buffer"`
Self User `json:"-"` Self User `json:"-"`
Client *http.Client `json:"-"` Client *http.Client `json:"-"`
shutdownChannel chan interface{} shutdownChannel chan interface{}
} }
@ -43,9 +42,9 @@ func NewBotAPI(token string) (*BotAPI, error) {
// It requires a token, provided by @BotFather on Telegram. // It requires a token, provided by @BotFather on Telegram.
func NewBotAPIWithClient(token string, client *http.Client) (*BotAPI, error) { func NewBotAPIWithClient(token string, client *http.Client) (*BotAPI, error) {
bot := &BotAPI{ bot := &BotAPI{
Token: token, Token: token,
Client: client, Client: client,
Buffer: 100, Buffer: 100,
shutdownChannel: make(chan interface{}), shutdownChannel: make(chan interface{}),
} }
@ -59,11 +58,31 @@ func NewBotAPIWithClient(token string, client *http.Client) (*BotAPI, error) {
return bot, nil return bot, nil
} }
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(APIEndpoint, bot.Token, endpoint) method := fmt.Sprintf(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
} }
@ -76,15 +95,20 @@ 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{apiResp.Description, parameters}
return apiResp, Error{
Message: apiResp.Description,
ResponseParameters: parameters,
}
} }
return apiResp, nil return apiResp, nil
@ -114,21 +138,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.
@ -137,7 +146,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) {
@ -186,6 +195,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(APIEndpoint, bot.Token, endpoint) method := fmt.Sprintf(APIEndpoint, bot.Token, endpoint)
req, err := http.NewRequest("POST", method, nil) req, err := http.NewRequest("POST", method, nil)
@ -207,7 +220,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
@ -249,11 +262,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, nil return user, err
} }
// IsMessageToMe returns true if message directed to this bot. // IsMessageToMe returns true if message directed to this bot.
@ -263,90 +274,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) {
switch c.(type) {
case Fileable:
return bot.sendFile(c.(Fileable))
default:
return bot.sendChattable(c)
}
}
// 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 { if err != nil {
return Message{}, err return APIResponse{}, err
}
message, err := bot.makeMessageRequest(method, v)
if err != nil {
return Message{}, err
} }
return message, nil switch t := c.(type) {
} case Fileable:
if t.useExistingFile() {
return bot.MakeRequest(t.method(), params)
}
// uploadAndSend will send a Message with a new file to Telegram. return bot.UploadFile(t.method(), params, t.name(), t.getFile())
func (bot *BotAPI) uploadAndSend(method string, config Fileable) (Message, error) { default:
params, err := config.params() return bot.MakeRequest(c.method(), params)
if err != nil {
return Message{}, err
} }
}
file := config.getFile() // Send will send a Chattable item to Telegram and provides the
// returned Message.
resp, err := bot.UploadFile(method, params, config.name(), file) func (bot *BotAPI) Send(c Chattable) (Message, error) {
resp, err := bot.Request(c)
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, nil return message, err
} }
// 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.
@ -354,46 +327,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, nil return profilePhotos, err
} }
// 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.
@ -404,71 +365,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, 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) return updates, err
}
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
} }
@ -480,7 +393,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() {
@ -490,7 +403,7 @@ func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (UpdatesChannel, error) {
return return
default: default:
} }
updates, err := bot.GetUpdates(config) updates, err := bot.GetUpdates(config)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
@ -509,7 +422,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
@ -536,96 +449,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 == "" {
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 { if err != nil {
return Chat{}, err return Chat{}, err
} }
@ -633,8 +461,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
} }
@ -642,16 +468,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 == "" {
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 { if err != nil {
return []ChatMember{}, err return []ChatMember{}, err
} }
@ -659,22 +479,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
} }
@ -682,23 +494,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
} }
@ -706,115 +509,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
} }
@ -825,65 +527,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 == "" {
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 { if err != nil {
return "", err return "", err
} }
@ -894,74 +542,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()
if err != nil {
return APIResponse{}, err
}
bot.debugLog(config.method(), v, nil)
return bot.MakeRequest(config.method(), v)
}
// UnpinChatMessage unpin message in supergroup
func (bot *BotAPI) UnpinChatMessage(config UnpinChatMessageConfig) (APIResponse, error) {
v, err := config.values()
if err != nil {
return APIResponse{}, err
}
bot.debugLog(config.method(), v, nil)
return bot.MakeRequest(config.method(), v)
}
// SetChatTitle change title of chat. resp, err := bot.MakeRequest(config.method(), params)
func (bot *BotAPI) SetChatTitle(config SetChatTitleConfig) (APIResponse, error) {
v, err := config.values()
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
} }
// SetChatDescription change description of chat. // StopPoll stops a poll and returns the result.
func (bot *BotAPI) SetChatDescription(config SetChatDescriptionConfig) (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)
}
// 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
} }

@ -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"
"git.tomans.ru/Tomansru/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()
bot.Request(RemoveWebhookConfig{})
} }
func TestUpdatesChan(t *testing.T) { func TestSendWithMediaGroup(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
var ucfg tgbotapi.UpdateConfig = tgbotapi.NewUpdate(0) cfg := NewMediaGroup(ChatID, []interface{}{
ucfg.Timeout = 60 NewInputMediaPhoto("https://i.imgur.com/unQLJIb.jpg"),
_, err := bot.GetUpdatesChan(ucfg) NewInputMediaPhoto("https://i.imgur.com/J5qweNZ.jpg"),
NewInputMediaVideo("https://i.imgur.com/F6RmI24.mp4"),
})
messages, err := bot.SendMediaGroup(cfg)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
}
func TestSendWithMediaGroup(t *testing.T) { if messages == nil {
bot, _ := getBot(t) t.Error()
}
cfg := tgbotapi.NewMediaGroup(ChatID, []interface{}{ if len(messages) != 3 {
tgbotapi.NewInputMediaPhoto("https://i.imgur.com/unQLJIb.jpg"), t.Error()
tgbotapi.NewInputMediaPhoto("https://i.imgur.com/J5qweNZ.jpg"),
tgbotapi.NewInputMediaVideo("https://i.imgur.com/F6RmI24.mp4"),
})
_, err := bot.Send(cfg)
if err != nil {
t.Error(err)
} }
} }
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 := 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 { if err != nil {
t.Error(err) t.Error(err)
t.Fail() 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()
}
} }

File diff suppressed because it is too large Load Diff

@ -1,5 +1,3 @@
module git.tomans.ru/Tomansru/telegram-bot-api module github.com/go-telegram-bot-api/telegram-bot-api/v5
go 1.13
require github.com/technoweenie/multipartstreamer v1.0.1 require github.com/technoweenie/multipartstreamer v1.0.1

@ -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,
},
} }
} }
@ -705,7 +737,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,
@ -747,3 +779,59 @@ 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,
}
}
// NewStopPoll allows you to stop a poll.
func NewStopPoll(chatID int64, messageID int) StopPollConfig {
return StopPollConfig{
BaseEdit{
ChatID: chatID,
MessageID: messageID,
},
}
}

@ -1,48 +1,46 @@
package tgbotapi_test package tgbotapi
import ( import (
"testing" "testing"
"git.tomans.ru/Tomansru/telegram-bot-api"
) )
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" ||
@ -52,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" ||
@ -62,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" ||
@ -72,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" ||
@ -83,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" ||
@ -93,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" ||
@ -104,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" ||
@ -115,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" ||
@ -127,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" ||
@ -139,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 ||
@ -149,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 ||
@ -159,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 ||

@ -0,0 +1,100 @@
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)
}
}
// AddNonNilBool adds the value of a bool pointer if not nil.
func (p Params) AddNonNilBool(key string, value *bool) {
if value != nil {
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)
}
case int64:
if v != 0 {
p[key] = strconv.FormatInt(v, 10)
}
case string:
if v != "" {
p[key] = v
}
case nil:
default:
b, err := json.Marshal(arg)
if err != nil {
return err
}
p[key] = string(b)
}
}
return nil
}

@ -37,6 +37,7 @@ 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"`
} }
// UpdatesChannel is the channel for getting updates. // UpdatesChannel is the channel for getting updates.
@ -97,9 +98,12 @@ type Chat struct {
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"` // 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
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.
@ -130,45 +134,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
Audio *Audio `json:"audio"` // optional MediaGroupID string `json:"media_group_id"` // optional
Document *Document `json:"document"` // optional AuthorSignature string `json:"author_signature"` // optional
Animation *ChatAnimation `json:"animation"` // optional Text string `json:"text"` // optional
Game *Game `json:"game"` // optional Entities []MessageEntity `json:"entities"` // optional
Photo *[]PhotoSize `json:"photo"` // optional CaptionEntities []MessageEntity `json:"caption_entities"` // optional
Sticker *Sticker `json:"sticker"` // optional Audio *Audio `json:"audio"` // optional
Video *Video `json:"video"` // optional Document *Document `json:"document"` // optional
VideoNote *VideoNote `json:"video_note"` // optional Animation *ChatAnimation `json:"animation"` // optional
Voice *Voice `json:"voice"` // optional Game *Game `json:"game"` // optional
Caption string `json:"caption"` // optional Photo []PhotoSize `json:"photo"` // optional
Contact *Contact `json:"contact"` // optional Sticker *Sticker `json:"sticker"` // optional
Location *Location `json:"location"` // optional Video *Video `json:"video"` // optional
Venue *Venue `json:"venue"` // optional VideoNote *VideoNote `json:"video_note"` // optional
NewChatMembers *[]User `json:"new_chat_members"` // optional Voice *Voice `json:"voice"` // optional
LeftChatMember *User `json:"left_chat_member"` // optional Caption string `json:"caption"` // optional
NewChatTitle string `json:"new_chat_title"` // optional Contact *Contact `json:"contact"` // optional
NewChatPhoto *[]PhotoSize `json:"new_chat_photo"` // optional Location *Location `json:"location"` // optional
DeleteChatPhoto bool `json:"delete_chat_photo"` // optional Venue *Venue `json:"venue"` // optional
GroupChatCreated bool `json:"group_chat_created"` // optional Poll *Poll `json:"poll"` // optional
SuperGroupChatCreated bool `json:"supergroup_chat_created"` // optional NewChatMembers []User `json:"new_chat_members"` // optional
ChannelChatCreated bool `json:"channel_chat_created"` // optional LeftChatMember *User `json:"left_chat_member"` // optional
MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional NewChatTitle string `json:"new_chat_title"` // optional
MigrateFromChatID int64 `json:"migrate_from_chat_id"` // optional NewChatPhoto []PhotoSize `json:"new_chat_photo"` // optional
PinnedMessage *Message `json:"pinned_message"` // optional DeleteChatPhoto bool `json:"delete_chat_photo"` // optional
Invoice *Invoice `json:"invoice"` // optional GroupChatCreated bool `json:"group_chat_created"` // optional
SuccessfulPayment *SuccessfulPayment `json:"successful_payment"` // optional SuperGroupChatCreated bool `json:"supergroup_chat_created"` // optional
PassportData *PassportData `json:"passport_data,omitempty"` // optional ChannelChatCreated bool `json:"channel_chat_created"` // optional
MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional
MigrateFromChatID int64 `json:"migrate_from_chat_id"` // optional
PinnedMessage *Message `json:"pinned_message"` // optional
Invoice *Invoice `json:"invoice"` // optional
SuccessfulPayment *SuccessfulPayment `json:"successful_payment"` // optional
ConnectedWebsite string `json:"connected_website"` // optional
PassportData *PassportData `json:"passport_data,omitempty"` // optional
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup"` // optional
} }
// Time converts the message timestamp into a Time. // Time converts the message timestamp into a Time.
@ -178,11 +190,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.Type == "bot_command" return entity.Offset == 0 && entity.Type == "bot_command"
} }
@ -212,7 +224,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]
} }
@ -231,7 +243,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
} }
@ -286,6 +299,22 @@ type Document struct {
// Sticker contains information about a sticker. // Sticker contains information about a sticker.
type Sticker struct { type Sticker struct {
FileID string `json:"file_id"`
Width int `json:"width"`
Height int `json:"height"`
Thumbnail *PhotoSize `json:"thumb"` // optional
Emoji string `json:"emoji"` // optional
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"` FileID string `json:"file_id"`
Width int `json:"width"` Width int `json:"width"`
Height int `json:"height"` Height int `json:"height"`
@ -343,6 +372,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.
@ -359,6 +389,20 @@ 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"`
}
// 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"`
}
// 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"`
@ -421,6 +465,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
@ -428,6 +473,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 {
@ -461,6 +514,7 @@ type ChatMember struct {
CanRestrictMembers bool `json:"can_restrict_members,omitempty"` // optional CanRestrictMembers bool `json:"can_restrict_members,omitempty"` // optional
CanPinMessages bool `json:"can_pin_messages,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
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
CanSendOtherMessages bool `json:"can_send_other_messages,omitempty"` // optional CanSendOtherMessages bool `json:"can_send_other_messages,omitempty"` // optional
@ -525,27 +579,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"`
@ -676,11 +709,27 @@ type InlineQueryResultDocument 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"`
@ -736,6 +785,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.
@ -773,9 +823,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.
@ -808,12 +858,64 @@ 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"`
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 {
Message string Message string
ResponseParameters ResponseParameters
} }
// Error message string.
func (e Error) Error() string { func (e Error) Error() string {
return e.Message return e.Message
} }

@ -1,14 +1,12 @@
package tgbotapi_test package tgbotapi
import ( import (
"testing" "testing"
"time" "time"
"git.tomans.ru/Tomansru/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()
@ -192,9 +190,50 @@ func TestChatIsSuperGroup(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 = ChatActionConfig{}
_ Chattable = ContactConfig{}
_ Chattable = DeleteChatPhotoConfig{}
_ Chattable = DeleteChatStickerSetConfig{}
_ Chattable = DeleteMessageConfig{}
_ Chattable = DocumentConfig{}
_ Chattable = EditMessageCaptionConfig{}
_ Chattable = EditMessageLiveLocationConfig{}
_ Chattable = EditMessageReplyMarkupConfig{}
_ Chattable = EditMessageTextConfig{}
_ Chattable = ForwardConfig{}
_ Chattable = GameConfig{}
_ Chattable = GetGameHighScoresConfig{}
_ Chattable = InlineConfig{}
_ Chattable = InvoiceConfig{}
_ Chattable = KickChatMemberConfig{}
_ Chattable = LocationConfig{}
_ Chattable = MediaGroupConfig{}
_ Chattable = MessageConfig{}
_ Chattable = PhotoConfig{}
_ Chattable = PinChatMessageConfig{}
_ Chattable = SetChatDescriptionConfig{}
_ Chattable = SetChatPhotoConfig{}
_ Chattable = SetChatTitleConfig{}
_ Chattable = SetGameScoreConfig{}
_ Chattable = StickerConfig{}
_ Chattable = UnpinChatMessageConfig{}
_ Chattable = UpdateConfig{}
_ Chattable = UserProfilePhotosConfig{}
_ Chattable = VenueConfig{}
_ Chattable = VideoConfig{}
_ Chattable = VideoNoteConfig{}
_ Chattable = VoiceConfig{}
_ Chattable = WebhookConfig{}
)