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

627
bot.go

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

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

File diff suppressed because it is too large Load Diff

@ -1,5 +1,3 @@
module git.tomans.ru/Tomansru/telegram-bot-api
go 1.13
module github.com/go-telegram-bot-api/telegram-bot-api/v5
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.
func NewMediaGroup(chatID int64, files []interface{}) MediaGroupConfig {
return MediaGroupConfig{
BaseChat: BaseChat{
ChatID: chatID,
},
InputMedia: files,
ChatID: chatID,
Media: files,
}
}
// NewInputMediaPhoto creates a new InputMediaPhoto.
func NewInputMediaPhoto(media string) InputMediaPhoto {
return InputMediaPhoto{
Type: "photo",
Media: media,
BaseInputMedia{
Type: "photo",
Media: media,
},
}
}
// NewInputMediaVideo creates a new InputMediaVideo.
func NewInputMediaVideo(media string) InputMediaVideo {
return InputMediaVideo{
Type: "video",
Media: media,
BaseInputMedia: BaseInputMedia{
Type: "video",
Media: media,
},
}
}
// NewInputMediaAnimation creates a new InputMediaAnimation.
func NewInputMediaAnimation(media string) InputMediaAnimation {
return InputMediaAnimation{
BaseInputMedia: BaseInputMedia{
Type: "animation",
Media: media,
},
}
}
// NewInputMediaAudio creates a new InputMediaAudio.
func NewInputMediaAudio(media string) InputMediaAudio {
return InputMediaAudio{
BaseInputMedia: BaseInputMedia{
Type: "audio",
Media: media,
},
}
}
// NewInputMediaDocument creates a new InputMediaDocument.
func NewInputMediaDocument(media string) InputMediaDocument {
return InputMediaDocument{
BaseInputMedia: BaseInputMedia{
Type: "document",
Media: media,
},
}
}
@ -705,7 +737,7 @@ func NewCallbackWithAlert(id, text string) CallbackConfig {
}
// NewInvoice creates a new Invoice request to the user.
func NewInvoice(chatID int64, title, description, payload, providerToken, startParameter, currency string, prices *[]LabeledPrice) InvoiceConfig {
func NewInvoice(chatID int64, title, description, payload, providerToken, startParameter, currency string, prices []LabeledPrice) InvoiceConfig {
return InvoiceConfig{
BaseChat: BaseChat{ChatID: chatID},
Title: title,
@ -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 (
"testing"
"git.tomans.ru/Tomansru/telegram-bot-api"
)
func TestNewInlineQueryResultArticle(t *testing.T) {
result := tgbotapi.NewInlineQueryResultArticle("id", "title", "message")
result := NewInlineQueryResultArticle("id", "title", "message")
if result.Type != "article" ||
result.ID != "id" ||
result.Title != "title" ||
result.InputMessageContent.(tgbotapi.InputTextMessageContent).Text != "message" {
result.InputMessageContent.(InputTextMessageContent).Text != "message" {
t.Fail()
}
}
func TestNewInlineQueryResultArticleMarkdown(t *testing.T) {
result := tgbotapi.NewInlineQueryResultArticleMarkdown("id", "title", "*message*")
result := NewInlineQueryResultArticleMarkdown("id", "title", "*message*")
if result.Type != "article" ||
result.ID != "id" ||
result.Title != "title" ||
result.InputMessageContent.(tgbotapi.InputTextMessageContent).Text != "*message*" ||
result.InputMessageContent.(tgbotapi.InputTextMessageContent).ParseMode != "Markdown" {
result.InputMessageContent.(InputTextMessageContent).Text != "*message*" ||
result.InputMessageContent.(InputTextMessageContent).ParseMode != "Markdown" {
t.Fail()
}
}
func TestNewInlineQueryResultArticleHTML(t *testing.T) {
result := tgbotapi.NewInlineQueryResultArticleHTML("id", "title", "<b>message</b>")
result := NewInlineQueryResultArticleHTML("id", "title", "<b>message</b>")
if result.Type != "article" ||
result.ID != "id" ||
result.Title != "title" ||
result.InputMessageContent.(tgbotapi.InputTextMessageContent).Text != "<b>message</b>" ||
result.InputMessageContent.(tgbotapi.InputTextMessageContent).ParseMode != "HTML" {
result.InputMessageContent.(InputTextMessageContent).Text != "<b>message</b>" ||
result.InputMessageContent.(InputTextMessageContent).ParseMode != "HTML" {
t.Fail()
}
}
func TestNewInlineQueryResultGIF(t *testing.T) {
result := tgbotapi.NewInlineQueryResultGIF("id", "google.com")
result := NewInlineQueryResultGIF("id", "google.com")
if result.Type != "gif" ||
result.ID != "id" ||
@ -52,7 +50,7 @@ func TestNewInlineQueryResultGIF(t *testing.T) {
}
func TestNewInlineQueryResultMPEG4GIF(t *testing.T) {
result := tgbotapi.NewInlineQueryResultMPEG4GIF("id", "google.com")
result := NewInlineQueryResultMPEG4GIF("id", "google.com")
if result.Type != "mpeg4_gif" ||
result.ID != "id" ||
@ -62,7 +60,7 @@ func TestNewInlineQueryResultMPEG4GIF(t *testing.T) {
}
func TestNewInlineQueryResultPhoto(t *testing.T) {
result := tgbotapi.NewInlineQueryResultPhoto("id", "google.com")
result := NewInlineQueryResultPhoto("id", "google.com")
if result.Type != "photo" ||
result.ID != "id" ||
@ -72,7 +70,7 @@ func TestNewInlineQueryResultPhoto(t *testing.T) {
}
func TestNewInlineQueryResultPhotoWithThumb(t *testing.T) {
result := tgbotapi.NewInlineQueryResultPhotoWithThumb("id", "google.com", "thumb.com")
result := NewInlineQueryResultPhotoWithThumb("id", "google.com", "thumb.com")
if result.Type != "photo" ||
result.ID != "id" ||
@ -83,7 +81,7 @@ func TestNewInlineQueryResultPhotoWithThumb(t *testing.T) {
}
func TestNewInlineQueryResultVideo(t *testing.T) {
result := tgbotapi.NewInlineQueryResultVideo("id", "google.com")
result := NewInlineQueryResultVideo("id", "google.com")
if result.Type != "video" ||
result.ID != "id" ||
@ -93,7 +91,7 @@ func TestNewInlineQueryResultVideo(t *testing.T) {
}
func TestNewInlineQueryResultAudio(t *testing.T) {
result := tgbotapi.NewInlineQueryResultAudio("id", "google.com", "title")
result := NewInlineQueryResultAudio("id", "google.com", "title")
if result.Type != "audio" ||
result.ID != "id" ||
@ -104,7 +102,7 @@ func TestNewInlineQueryResultAudio(t *testing.T) {
}
func TestNewInlineQueryResultVoice(t *testing.T) {
result := tgbotapi.NewInlineQueryResultVoice("id", "google.com", "title")
result := NewInlineQueryResultVoice("id", "google.com", "title")
if result.Type != "voice" ||
result.ID != "id" ||
@ -115,7 +113,7 @@ func TestNewInlineQueryResultVoice(t *testing.T) {
}
func TestNewInlineQueryResultDocument(t *testing.T) {
result := tgbotapi.NewInlineQueryResultDocument("id", "google.com", "title", "mime/type")
result := NewInlineQueryResultDocument("id", "google.com", "title", "mime/type")
if result.Type != "document" ||
result.ID != "id" ||
@ -127,7 +125,7 @@ func TestNewInlineQueryResultDocument(t *testing.T) {
}
func TestNewInlineQueryResultLocation(t *testing.T) {
result := tgbotapi.NewInlineQueryResultLocation("id", "name", 40, 50)
result := NewInlineQueryResultLocation("id", "name", 40, 50)
if result.Type != "location" ||
result.ID != "id" ||
@ -139,7 +137,7 @@ func TestNewInlineQueryResultLocation(t *testing.T) {
}
func TestNewEditMessageText(t *testing.T) {
edit := tgbotapi.NewEditMessageText(ChatID, ReplyToMessageID, "new text")
edit := NewEditMessageText(ChatID, ReplyToMessageID, "new text")
if edit.Text != "new text" ||
edit.BaseEdit.ChatID != ChatID ||
@ -149,7 +147,7 @@ func TestNewEditMessageText(t *testing.T) {
}
func TestNewEditMessageCaption(t *testing.T) {
edit := tgbotapi.NewEditMessageCaption(ChatID, ReplyToMessageID, "new caption")
edit := NewEditMessageCaption(ChatID, ReplyToMessageID, "new caption")
if edit.Caption != "new caption" ||
edit.BaseEdit.ChatID != ChatID ||
@ -159,15 +157,15 @@ func TestNewEditMessageCaption(t *testing.T) {
}
func TestNewEditMessageReplyMarkup(t *testing.T) {
markup := tgbotapi.InlineKeyboardMarkup{
InlineKeyboard: [][]tgbotapi.InlineKeyboardButton{
[]tgbotapi.InlineKeyboardButton{
tgbotapi.InlineKeyboardButton{Text: "test"},
markup := InlineKeyboardMarkup{
InlineKeyboard: [][]InlineKeyboardButton{
[]InlineKeyboardButton{
InlineKeyboardButton{Text: "test"},
},
},
}
edit := tgbotapi.NewEditMessageReplyMarkup(ChatID, ReplyToMessageID, markup)
edit := NewEditMessageReplyMarkup(ChatID, ReplyToMessageID, markup)
if edit.ReplyMarkup.InlineKeyboard[0][0].Text != "test" ||
edit.BaseEdit.ChatID != ChatID ||

@ -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"`
ShippingQuery *ShippingQuery `json:"shipping_query"`
PreCheckoutQuery *PreCheckoutQuery `json:"pre_checkout_query"`
Poll *Poll `json:"poll"`
}
// UpdatesChannel is the channel for getting updates.
@ -97,9 +98,12 @@ type Chat struct {
FirstName string `json:"first_name"` // optional
LastName string `json:"last_name"` // optional
AllMembersAreAdmins bool `json:"all_members_are_administrators"` // optional
Photo *ChatPhoto `json:"photo"`
Description string `json:"description,omitempty"` // optional
InviteLink string `json:"invite_link,omitempty"` // optional
Photo *ChatPhoto `json:"photo"` // optional
Description string `json:"description,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.
@ -130,45 +134,53 @@ func (c Chat) ChatConfig() ChatConfig {
// Message is returned by almost every request, and contains data about
// almost anything.
type Message struct {
MessageID int `json:"message_id"`
From *User `json:"from"` // optional
Date int `json:"date"`
Chat *Chat `json:"chat"`
ForwardFrom *User `json:"forward_from"` // optional
ForwardFromChat *Chat `json:"forward_from_chat"` // optional
ForwardFromMessageID int `json:"forward_from_message_id"` // optional
ForwardDate int `json:"forward_date"` // optional
ReplyToMessage *Message `json:"reply_to_message"` // optional
EditDate int `json:"edit_date"` // optional
Text string `json:"text"` // optional
Entities *[]MessageEntity `json:"entities"` // optional
Audio *Audio `json:"audio"` // optional
Document *Document `json:"document"` // optional
Animation *ChatAnimation `json:"animation"` // optional
Game *Game `json:"game"` // optional
Photo *[]PhotoSize `json:"photo"` // optional
Sticker *Sticker `json:"sticker"` // optional
Video *Video `json:"video"` // optional
VideoNote *VideoNote `json:"video_note"` // optional
Voice *Voice `json:"voice"` // optional
Caption string `json:"caption"` // optional
Contact *Contact `json:"contact"` // optional
Location *Location `json:"location"` // optional
Venue *Venue `json:"venue"` // optional
NewChatMembers *[]User `json:"new_chat_members"` // optional
LeftChatMember *User `json:"left_chat_member"` // optional
NewChatTitle string `json:"new_chat_title"` // optional
NewChatPhoto *[]PhotoSize `json:"new_chat_photo"` // optional
DeleteChatPhoto bool `json:"delete_chat_photo"` // optional
GroupChatCreated bool `json:"group_chat_created"` // optional
SuperGroupChatCreated bool `json:"supergroup_chat_created"` // optional
ChannelChatCreated bool `json:"channel_chat_created"` // optional
MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional
MigrateFromChatID int64 `json:"migrate_from_chat_id"` // optional
PinnedMessage *Message `json:"pinned_message"` // optional
Invoice *Invoice `json:"invoice"` // optional
SuccessfulPayment *SuccessfulPayment `json:"successful_payment"` // optional
PassportData *PassportData `json:"passport_data,omitempty"` // optional
MessageID int `json:"message_id"`
From *User `json:"from"` // optional
Date int `json:"date"`
Chat *Chat `json:"chat"`
ForwardFrom *User `json:"forward_from"` // optional
ForwardFromChat *Chat `json:"forward_from_chat"` // optional
ForwardFromMessageID int `json:"forward_from_message_id"` // optional
ForwardSignature string `json:"forward_signature"` // optional
ForwardSenderName string `json:"forward_sender_name"` // optional
ForwardDate int `json:"forward_date"` // optional
ReplyToMessage *Message `json:"reply_to_message"` // optional
EditDate int `json:"edit_date"` // optional
MediaGroupID string `json:"media_group_id"` // optional
AuthorSignature string `json:"author_signature"` // optional
Text string `json:"text"` // optional
Entities []MessageEntity `json:"entities"` // optional
CaptionEntities []MessageEntity `json:"caption_entities"` // optional
Audio *Audio `json:"audio"` // optional
Document *Document `json:"document"` // optional
Animation *ChatAnimation `json:"animation"` // optional
Game *Game `json:"game"` // optional
Photo []PhotoSize `json:"photo"` // optional
Sticker *Sticker `json:"sticker"` // optional
Video *Video `json:"video"` // optional
VideoNote *VideoNote `json:"video_note"` // optional
Voice *Voice `json:"voice"` // optional
Caption string `json:"caption"` // optional
Contact *Contact `json:"contact"` // optional
Location *Location `json:"location"` // optional
Venue *Venue `json:"venue"` // optional
Poll *Poll `json:"poll"` // optional
NewChatMembers []User `json:"new_chat_members"` // optional
LeftChatMember *User `json:"left_chat_member"` // optional
NewChatTitle string `json:"new_chat_title"` // optional
NewChatPhoto []PhotoSize `json:"new_chat_photo"` // optional
DeleteChatPhoto bool `json:"delete_chat_photo"` // optional
GroupChatCreated bool `json:"group_chat_created"` // optional
SuperGroupChatCreated bool `json:"supergroup_chat_created"` // optional
ChannelChatCreated bool `json:"channel_chat_created"` // optional
MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional
MigrateFromChatID int64 `json:"migrate_from_chat_id"` // optional
PinnedMessage *Message `json:"pinned_message"` // optional
Invoice *Invoice `json:"invoice"` // optional
SuccessfulPayment *SuccessfulPayment `json:"successful_payment"` // optional
ConnectedWebsite string `json:"connected_website"` // optional
PassportData *PassportData `json:"passport_data,omitempty"` // optional
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup"` // optional
}
// Time converts the message timestamp into a Time.
@ -178,11 +190,11 @@ func (m *Message) Time() time.Time {
// IsCommand returns true if message starts with a "bot_command" entity.
func (m *Message) IsCommand() bool {
if m.Entities == nil || len(*m.Entities) == 0 {
if m.Entities == nil || len(m.Entities) == 0 {
return false
}
entity := (*m.Entities)[0]
entity := m.Entities[0]
return entity.Offset == 0 && entity.Type == "bot_command"
}
@ -212,7 +224,7 @@ func (m *Message) CommandWithAt() string {
}
// IsCommand() checks that the message begins with a bot_command entity
entity := (*m.Entities)[0]
entity := m.Entities[0]
return m.Text[1:entity.Length]
}
@ -231,7 +243,8 @@ func (m *Message) CommandArguments() string {
}
// IsCommand() checks that the message begins with a bot_command entity
entity := (*m.Entities)[0]
entity := m.Entities[0]
if len(m.Text) == entity.Length {
return "" // The command makes up the whole message
}
@ -286,6 +299,22 @@ type Document struct {
// Sticker contains information about a sticker.
type Sticker struct {
FileID string `json:"file_id"`
Width int `json:"width"`
Height int `json:"height"`
Thumbnail *PhotoSize `json:"thumb"` // optional
Emoji string `json:"emoji"` // optional
SetName string `json:"set_name"` // optional
MaskPosition MaskPosition `json:"mask_position"` //optional
FileSize int `json:"file_size"` // optional
}
// MaskPosition is the position of a mask.
type MaskPosition struct {
Point string `json:"point"`
XShift float32 `json:"x_shift"`
YShift float32 `json:"y_shift"`
Scale float32 `json:"scale"`
FileID string `json:"file_id"`
Width int `json:"width"`
Height int `json:"height"`
@ -343,6 +372,7 @@ type Contact struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"` // optional
UserID int `json:"user_id"` // optional
VCard string `json:"vcard"` // optional
}
// Location contains information about a place.
@ -359,6 +389,20 @@ type Venue struct {
FoursquareID string `json:"foursquare_id"` // optional
}
// PollOption contains information about one answer option in a poll.
type PollOption struct {
Text string `json:"text"`
VoterCount int `json:"voter_count"`
}
// 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.
type UserProfilePhotos struct {
TotalCount int `json:"total_count"`
@ -421,6 +465,7 @@ type InlineKeyboardMarkup struct {
type InlineKeyboardButton struct {
Text string `json:"text"`
URL *string `json:"url,omitempty"` // optional
LoginURL *LoginURL `json:"login_url,omitempty"` // optional
CallbackData *string `json:"callback_data,omitempty"` // optional
SwitchInlineQuery *string `json:"switch_inline_query,omitempty"` // optional
SwitchInlineQueryCurrentChat *string `json:"switch_inline_query_current_chat,omitempty"` // optional
@ -428,6 +473,14 @@ type InlineKeyboardButton struct {
Pay bool `json:"pay,omitempty"` // optional
}
// LoginURL is the parameters for the login inline keyboard button type.
type LoginURL struct {
URL string `json:"url"`
ForwardText string `json:"forward_text"`
BotUsername string `json:"bot_username"`
RequestWriteAccess bool `json:"request_write_access"`
}
// CallbackQuery is data sent when a keyboard button with callback data
// is clicked.
type CallbackQuery struct {
@ -461,6 +514,7 @@ type ChatMember struct {
CanRestrictMembers bool `json:"can_restrict_members,omitempty"` // optional
CanPinMessages bool `json:"can_pin_messages,omitempty"` // optional
CanPromoteMembers bool `json:"can_promote_members,omitempty"` // optional
IsChatMember bool `json:"is_member"` // optional
CanSendMessages bool `json:"can_send_messages,omitempty"` // optional
CanSendMediaMessages bool `json:"can_send_media_messages,omitempty"` // optional
CanSendOtherMessages bool `json:"can_send_other_messages,omitempty"` // optional
@ -525,27 +579,6 @@ func (info WebhookInfo) IsSet() bool {
return info.URL != ""
}
// InputMediaPhoto contains a photo for displaying as part of a media group.
type InputMediaPhoto struct {
Type string `json:"type"`
Media string `json:"media"`
Caption string `json:"caption"`
ParseMode string `json:"parse_mode"`
}
// InputMediaVideo contains a video for displaying as part of a media group.
type InputMediaVideo struct {
Type string `json:"type"`
Media string `json:"media"`
// thumb intentionally missing as it is not currently compatible
Caption string `json:"caption"`
ParseMode string `json:"parse_mode"`
Width int `json:"width"`
Height int `json:"height"`
Duration int `json:"duration"`
SupportsStreaming bool `json:"supports_streaming"`
}
// InlineQuery is a Query from Telegram for an inline request.
type InlineQuery struct {
ID string `json:"id"`
@ -676,11 +709,27 @@ type InlineQueryResultDocument struct {
// InlineQueryResultLocation is an inline query response location.
type InlineQueryResultLocation struct {
Type string `json:"type"` // required
ID string `json:"id"` // required
Latitude float64 `json:"latitude"` // required
Longitude float64 `json:"longitude"` // required
Title string `json:"title"` // required
Type string `json:"type"` // required
ID string `json:"id"` // required
Latitude float64 `json:"latitude"` // required
Longitude float64 `json:"longitude"` // required
LivePeriod int `json:"live_period"` // optional
Title string `json:"title"` // required
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
InputMessageContent interface{} `json:"input_message_content,omitempty"`
ThumbURL string `json:"thumb_url"`
ThumbWidth int `json:"thumb_width"`
ThumbHeight int `json:"thumb_height"`
}
// InlineQueryResultContact is an inline query response contact.
type InlineQueryResultContact struct {
Type string `json:"type"` // required
ID string `json:"id"` // required
PhoneNumber string `json:"phone_number"` // required
FirstName string `json:"first_name"` // required
LastName string `json:"last_name"`
VCard string `json:"vcard"`
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
InputMessageContent interface{} `json:"input_message_content,omitempty"`
ThumbURL string `json:"thumb_url"`
@ -736,6 +785,7 @@ type InputContactMessageContent struct {
PhoneNumber string `json:"phone_number"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
VCard string `json:"vcard"`
}
// Invoice contains basic information about an invoice.
@ -773,9 +823,9 @@ type OrderInfo struct {
// ShippingOption represents one shipping option.
type ShippingOption struct {
ID string `json:"id"`
Title string `json:"title"`
Prices *[]LabeledPrice `json:"prices"`
ID string `json:"id"`
Title string `json:"title"`
Prices []LabeledPrice `json:"prices"`
}
// SuccessfulPayment contains basic information about a successful payment.
@ -808,12 +858,64 @@ type PreCheckoutQuery struct {
OrderInfo *OrderInfo `json:"order_info,omitempty"`
}
// StickerSet is a collection of stickers.
type StickerSet struct {
Name string `json:"name"`
Title string `json:"title"`
ContainsMasks bool `json:"contains_masks"`
Stickers []Sticker `json:"stickers"`
}
// BaseInputMedia is a base type for the InputMedia types.
type BaseInputMedia struct {
Type string `json:"type"`
Media string `json:"media"`
Caption string `json:"caption"`
ParseMode string `json:"parse_mode"`
}
// InputMediaPhoto is a photo to send as part of a media group.
type InputMediaPhoto struct {
BaseInputMedia
}
// InputMediaVideo is a video to send as part of a media group.
type InputMediaVideo struct {
BaseInputMedia
Width int `json:"width"`
Height int `json:"height"`
Duration int `json:"duration"`
SupportsStreaming bool `json:"supports_streaming"`
}
// InputMediaAnimation is an animation to send as part of a media group.
type InputMediaAnimation struct {
BaseInputMedia
Width int `json:"width"`
Height int `json:"height"`
Duration int `json:"duration"`
}
// InputMediaAudio is a audio to send as part of a media group.
type InputMediaAudio struct {
BaseInputMedia
Duration int `json:"duration"`
Performer string `json:"performer"`
Title string `json:"title"`
}
// InputMediaDocument is a audio to send as part of a media group.
type InputMediaDocument struct {
BaseInputMedia
}
// Error is an error containing extra information returned by the Telegram API.
type Error struct {
Message string
ResponseParameters
}
// Error message string.
func (e Error) Error() string {
return e.Message
}

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