diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 00000000..48b2859b
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,33 @@
+name: Test
+
+on:
+ push:
+ branches:
+ - master
+ - develop
+ pull_request:
+
+jobs:
+ build:
+ name: Test
+ runs-on: ubuntu-latest
+ steps:
+ - name: Set up Go 1.x
+ uses: actions/setup-go@v2
+ with:
+ go-version: ^1.15
+ id: go
+
+ - name: Check out code into the Go module directory
+ uses: actions/checkout@v2
+
+ - name: Build
+ run: go build -v .
+
+ - name: Test
+ run: go test -coverprofile=coverage.out -covermode=atomic -v .
+
+ - name: Upload coverage report
+ uses: codecov/codecov-action@v1
+ with:
+ file: ./coverage.out
diff --git a/.gitignore b/.gitignore
index fb5a5e83..eb7a23b2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
.idea/
coverage.out
tmp/
+book/
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 712ce95a..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-language: go
-
-go:
- - '1.13'
- - '1.14'
- - tip
diff --git a/README.md b/README.md
index 43b33ed8..536077f2 100644
--- a/README.md
+++ b/README.md
@@ -62,60 +62,7 @@ func main() {
}
```
-There are more examples on the [wiki](https://github.com/go-telegram-bot-api/telegram-bot-api/wiki)
+There are more examples on the [site](https://go-telegram-bot-api.github.io/)
with detailed information on how to do many different kinds of things.
It's a great place to get started on using keyboards, commands, or other
kinds of reply markup.
-
-If you need to use webhooks (if you wish to run on Google App Engine),
-you may use a slightly different method.
-
-```go
-package main
-
-import (
- "log"
- "net/http"
-
- "github.com/go-telegram-bot-api/telegram-bot-api"
-)
-
-func main() {
- bot, err := tgbotapi.NewBotAPI("MyAwesomeBotToken")
- if err != nil {
- log.Fatal(err)
- }
-
- bot.Debug = true
-
- log.Printf("Authorized on account %s", bot.Self.UserName)
-
- _, err = bot.SetWebhook(tgbotapi.NewWebhookWithCert("https://www.google.com:8443/"+bot.Token, "cert.pem"))
- if err != nil {
- log.Fatal(err)
- }
- info, err := bot.GetWebhookInfo()
- if err != nil {
- log.Fatal(err)
- }
- if info.LastErrorDate != 0 {
- log.Printf("Telegram callback failed: %s", info.LastErrorMessage)
- }
- updates := bot.ListenForWebhook("/" + bot.Token)
- go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil)
-
- for update := range updates {
- log.Printf("%+v\n", update)
- }
-}
-```
-
-If you need, you may generate a self signed certficate, as this requires
-HTTPS / TLS. The above example tells Telegram that this is your
-certificate and that it should be trusted, even though it is not
-properly signed.
-
- openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 3560 -subj "//O=Org\CN=Test" -nodes
-
-Now that [Let's Encrypt](https://letsencrypt.org) is available,
-you may wish to generate your free TLS certificate there.
diff --git a/book.toml b/book.toml
new file mode 100644
index 00000000..841d5ba6
--- /dev/null
+++ b/book.toml
@@ -0,0 +1,9 @@
+[book]
+authors = ["Syfaro"]
+language = "en"
+multilingual = false
+src = "docs"
+title = "Go Telegram Bot API"
+
+[output.html]
+git-repository-url = "https://github.com/go-telegram-bot-api/telegram-bot-api"
diff --git a/bot.go b/bot.go
index 626024e1..47e3bb3a 100644
--- a/bot.go
+++ b/bot.go
@@ -3,23 +3,20 @@
package tgbotapi
import (
- "bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
+ "mime/multipart"
"net/http"
"net/url"
- "os"
- "strconv"
"strings"
"time"
-
- "github.com/technoweenie/multipartstreamer"
)
-type HttpClient interface {
+// HTTPClient is the type needed for the bot to perform HTTP requests.
+type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}
@@ -30,7 +27,7 @@ type BotAPI struct {
Buffer int `json:"buffer"`
Self User `json:"-"`
- Client HttpClient `json:"-"`
+ Client HTTPClient `json:"-"`
shutdownChannel chan interface{}
apiEndpoint string
@@ -55,7 +52,7 @@ func NewBotAPIWithAPIEndpoint(token, apiEndpoint string) (*BotAPI, error) {
// and allows you to pass a http.Client.
//
// It requires a token, provided by @BotFather on Telegram and API endpoint.
-func NewBotAPIWithClient(token, apiEndpoint string, client HttpClient) (*BotAPI, error) {
+func NewBotAPIWithClient(token, apiEndpoint string, client HTTPClient) (*BotAPI, error) {
bot := &BotAPI{
Token: token,
Client: client,
@@ -75,46 +72,72 @@ func NewBotAPIWithClient(token, apiEndpoint string, client HttpClient) (*BotAPI,
return bot, nil
}
-// SetAPIEndpoint add telegram apiEndpont to Bot
+// SetAPIEndpoint changes the Telegram Bot API endpoint used by the instance.
func (bot *BotAPI) SetAPIEndpoint(apiEndpoint string) {
bot.apiEndpoint = apiEndpoint
}
+func buildParams(in Params) url.Values {
+ if in == nil {
+ return url.Values{}
+ }
+
+ out := url.Values{}
+
+ for key, value := range in {
+ out.Set(key, value)
+ }
+
+ return out
+}
+
// MakeRequest makes a request to a specific endpoint with our token.
-func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse, error) {
+func (bot *BotAPI) MakeRequest(endpoint string, params Params) (*APIResponse, error) {
+ if bot.Debug {
+ log.Printf("Endpoint: %s, params: %v\n", endpoint, params)
+ }
+
method := fmt.Sprintf(bot.apiEndpoint, bot.Token, endpoint)
- req, err := http.NewRequest("POST", method, strings.NewReader(params.Encode()))
+ values := buildParams(params)
+
+ req, err := http.NewRequest("POST", method, strings.NewReader(values.Encode()))
if err != nil {
- return APIResponse{}, err
+ return &APIResponse{}, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := bot.Client.Do(req)
if err != nil {
- return APIResponse{}, err
+ return nil, err
}
defer resp.Body.Close()
var apiResp APIResponse
bytes, err := bot.decodeAPIResponse(resp.Body, &apiResp)
if err != nil {
- return apiResp, err
+ return &apiResp, err
}
if bot.Debug {
- log.Printf("%s resp: %s", endpoint, bytes)
+ log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes))
}
if !apiResp.Ok {
- parameters := ResponseParameters{}
+ var parameters ResponseParameters
+
if apiResp.Parameters != nil {
parameters = *apiResp.Parameters
}
- return apiResp, Error{Code: apiResp.ErrorCode, Message: apiResp.Description, ResponseParameters: parameters}
+
+ return &apiResp, &Error{
+ Code: apiResp.ErrorCode,
+ Message: apiResp.Description,
+ ResponseParameters: parameters,
+ }
}
- return apiResp, nil
+ return &apiResp, nil
}
// decodeAPIResponse decode response and return slice of bytes if debug enabled.
@@ -141,118 +164,103 @@ 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.
-// File should be a string to a file path, a FileBytes struct,
-// a FileReader struct, or a url.URL.
-//
-// Note that if your FileReader has a size set to -1, it will read
-// the file into memory to calculate a size.
-func (bot *BotAPI) UploadFile(endpoint string, params map[string]string, fieldname string, file interface{}) (APIResponse, error) {
- ms := multipartstreamer.New()
-
- switch f := file.(type) {
- case string:
- ms.WriteFields(params)
+// UploadFiles makes a request to the API with files.
+func (bot *BotAPI) UploadFiles(endpoint string, params Params, files []RequestFile) (*APIResponse, error) {
+ r, w := io.Pipe()
+ m := multipart.NewWriter(w)
- fileHandle, err := os.Open(f)
- if err != nil {
- return APIResponse{}, err
- }
- defer fileHandle.Close()
+ // This code modified from the very helpful @HirbodBehnam
+ // https://github.com/go-telegram-bot-api/telegram-bot-api/issues/354#issuecomment-663856473
+ go func() {
+ defer w.Close()
+ defer m.Close()
- fi, err := os.Stat(f)
- if err != nil {
- return APIResponse{}, err
+ for field, value := range params {
+ if err := m.WriteField(field, value); err != nil {
+ w.CloseWithError(err)
+ return
+ }
}
- ms.WriteReader(fieldname, fileHandle.Name(), fi.Size(), fileHandle)
- case FileBytes:
- ms.WriteFields(params)
+ for _, file := range files {
+ if file.Data.NeedsUpload() {
+ name, reader, err := file.Data.UploadData()
+ if err != nil {
+ w.CloseWithError(err)
+ return
+ }
- buf := bytes.NewBuffer(f.Bytes)
- ms.WriteReader(fieldname, f.Name, int64(len(f.Bytes)), buf)
- case FileReader:
- ms.WriteFields(params)
+ part, err := m.CreateFormFile(file.Name, name)
+ if err != nil {
+ w.CloseWithError(err)
+ return
+ }
- if f.Size != -1 {
- ms.WriteReader(fieldname, f.Name, f.Size, f.Reader)
+ if _, err := io.Copy(part, reader); err != nil {
+ w.CloseWithError(err)
+ return
+ }
- break
- }
+ if closer, ok := reader.(io.ReadCloser); ok {
+ if err = closer.Close(); err != nil {
+ w.CloseWithError(err)
+ return
+ }
+ }
+ } else {
+ value := file.Data.SendData()
- data, err := ioutil.ReadAll(f.Reader)
- if err != nil {
- return APIResponse{}, err
+ if err := m.WriteField(file.Name, value); err != nil {
+ w.CloseWithError(err)
+ return
+ }
+ }
}
+ }()
- buf := bytes.NewBuffer(data)
-
- ms.WriteReader(fieldname, f.Name, int64(len(data)), buf)
- case url.URL:
- params[fieldname] = f.String()
-
- ms.WriteFields(params)
- default:
- return APIResponse{}, errors.New(ErrBadFileType)
+ if bot.Debug {
+ log.Printf("Endpoint: %s, params: %v, with %d files\n", endpoint, params, len(files))
}
method := fmt.Sprintf(bot.apiEndpoint, bot.Token, endpoint)
- req, err := http.NewRequest("POST", method, nil)
+ req, err := http.NewRequest("POST", method, r)
if err != nil {
- return APIResponse{}, err
+ return nil, err
}
- ms.SetupRequest(req)
+ req.Header.Set("Content-Type", m.FormDataContentType())
- res, err := bot.Client.Do(req)
+ resp, err := bot.Client.Do(req)
if err != nil {
- return APIResponse{}, err
+ return nil, err
}
- defer res.Body.Close()
+ defer resp.Body.Close()
- bytes, err := ioutil.ReadAll(res.Body)
+ var apiResp APIResponse
+ bytes, err := bot.decodeAPIResponse(resp.Body, &apiResp)
if err != nil {
- return APIResponse{}, err
+ return &apiResp, err
}
if bot.Debug {
- log.Println(string(bytes))
- }
-
- var apiResp APIResponse
-
- err = json.Unmarshal(bytes, &apiResp)
- if err != nil {
- return APIResponse{}, err
+ log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes))
}
if !apiResp.Ok {
- parameters := ResponseParameters{}
+ var parameters ResponseParameters
+
if apiResp.Parameters != nil {
parameters = *apiResp.Parameters
}
- return apiResp, Error{Code: apiResp.ErrorCode, Message: apiResp.Description, ResponseParameters: parameters}
+
+ return &apiResp, &Error{
+ Message: apiResp.Description,
+ ResponseParameters: parameters,
+ }
}
- return apiResp, nil
+ return &apiResp, nil
}
// GetFileDirectURL returns direct URL to file
@@ -280,11 +288,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.
@@ -294,90 +300,67 @@ 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)
+func hasFilesNeedingUpload(files []RequestFile) bool {
+ for _, file := range files {
+ if file.Data.NeedsUpload() {
+ return true
+ }
}
-}
-// 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)
- }
+ return false
}
-// 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
+ return nil, err
}
- message, err := bot.makeMessageRequest(method, v)
- if err != nil {
- return Message{}, err
- }
+ if t, ok := c.(Fileable); ok {
+ files := t.files()
- return message, nil
-}
+ // If we have files that need to be uploaded, we should delegate the
+ // request to UploadFile.
+ if hasFilesNeedingUpload(files) {
+ return bot.UploadFiles(t.method(), params, files)
+ }
-// 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
+ // However, if there are no files to be uploaded, there's likely things
+ // that need to be turned into params instead.
+ for _, file := range files {
+ params[file.Name] = file.Data.SendData()
+ }
}
- file := config.getFile()
+ return bot.MakeRequest(c.method(), params)
+}
- 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)
- }
-
- return bot.uploadAndSend(config.method(), config)
-}
-
-// sendChattable sends a Chattable.
-func (bot *BotAPI) sendChattable(config Chattable) (Message, error) {
- v, err := config.values()
+// SendMediaGroup sends a media group and returns the resulting messages.
+func (bot *BotAPI) SendMediaGroup(config MediaGroupConfig) ([]Message, error) {
+ resp, err := bot.Request(config)
if err != nil {
- return Message{}, err
+ return nil, err
}
- message, err := bot.makeMessageRequest(config.method(), v)
-
- if err != nil {
- return Message{}, err
- }
+ var messages []Message
+ err = json.Unmarshal(resp.Result, &messages)
- return message, nil
+ return messages, err
}
// GetUserProfilePhotos gets a user's profile photos.
@@ -385,121 +368,55 @@ 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))
- }
-
- resp, err := bot.MakeRequest("getUserProfilePhotos", v)
+ resp, err := bot.Request(config)
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)
-
- resp, err := bot.MakeRequest("getFile", v)
+ resp, err := bot.Request(config)
if err != nil {
return File{}, err
}
var file File
- json.Unmarshal(resp.Result, &file)
-
- bot.debugLog("GetFile", v, file)
+ err = json.Unmarshal(resp.Result, &file)
- return file, nil
+ return file, err
}
// GetUpdates fetches updates.
// If a WebHook is set, this will not return any data!
//
-// Offset, Limit, and Timeout are optional.
+// Offset, Limit, Timeout, and AllowedUpdates are optional.
// To avoid stale items, set Offset to one higher than the previous item.
// Set Timeout to a large number to reduce requests so you can get updates
// instantly instead of having to wait between requests.
func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) {
- v := url.Values{}
- if config.Offset != 0 {
- v.Add("offset", strconv.Itoa(config.Offset))
- }
- if config.Limit > 0 {
- v.Add("limit", strconv.Itoa(config.Limit))
- }
- if config.Timeout > 0 {
- v.Add("timeout", strconv.Itoa(config.Timeout))
- }
-
- resp, err := bot.MakeRequest("getUpdates", v)
+ resp, err := bot.Request(config)
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("deleteWebhook", 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
}
@@ -511,7 +428,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() {
@@ -541,7 +458,7 @@ func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (UpdatesChannel, error) {
}
}()
- return ch, nil
+ return ch
}
// StopReceivingUpdates stops the go routine which receives updates
@@ -588,96 +505,35 @@ func (bot *BotAPI) HandleUpdate(r *http.Request) (*Update, error) {
return &update, nil
}
-// AnswerInlineQuery sends a response to an inline query.
+// WriteToHTTPResponse writes the request to the HTTP ResponseWriter.
//
-// 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)
+// It doesn't support uploading files.
+//
+// See https://core.telegram.org/bots/api#making-requests-when-getting-updates
+// for details.
+func WriteToHTTPResponse(w http.ResponseWriter, c Chattable) error {
+ params, err := c.params()
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))
+ return err
}
- 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)
+ if t, ok := c.(Fileable); ok {
+ if hasFilesNeedingUpload(t.files()) {
+ return errors.New("unable to use http response to upload files")
+ }
}
- bot.debugLog("leaveChat", v, nil)
+ values := buildParams(params)
+ values.Set("method", c.method())
- return bot.MakeRequest("leaveChat", v)
+ w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
+ _, err = w.Write([]byte(values.Encode()))
+ return err
}
// 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)
- }
-
- resp, err := bot.MakeRequest("getChat", v)
+func (bot *BotAPI) GetChat(config ChatInfoConfig) (Chat, error) {
+ resp, err := bot.Request(config)
if err != nil {
return Chat{}, err
}
@@ -685,8 +541,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
}
@@ -694,16 +548,8 @@ 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)
- }
-
- resp, err := bot.MakeRequest("getChatAdministrators", v)
+func (bot *BotAPI) GetChatAdministrators(config ChatAdministratorsConfig) ([]ChatMember, error) {
+ resp, err := bot.Request(config)
if err != nil {
return []ChatMember{}, err
}
@@ -711,22 +557,12 @@ 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{}
-
- 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)
+func (bot *BotAPI) GetChatMembersCount(config ChatMemberCountConfig) (int, error) {
+ resp, err := bot.Request(config)
if err != nil {
return -1, err
}
@@ -734,23 +570,12 @@ 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{}
-
- 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)
+func (bot *BotAPI) GetChatMember(config GetChatMemberConfig) (ChatMember, error) {
+ resp, err := bot.Request(config)
if err != nil {
return ChatMember{}, err
}
@@ -758,115 +583,12 @@ 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()
-
- resp, err := bot.MakeRequest(config.method(), v)
+ resp, err := bot.Request(config)
if err != nil {
return []GameHighScore{}, err
}
@@ -877,65 +599,9 @@ 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_message", 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)
- }
-
- resp, err := bot.MakeRequest("exportChatInviteLink", v)
+func (bot *BotAPI) GetInviteLink(config ChatInviteLinkConfig) (string, error) {
+ resp, err := bot.Request(config)
if err != nil {
return "", err
}
@@ -946,124 +612,68 @@ func (bot *BotAPI) GetInviteLink(config ChatConfig) (string, error) {
return inviteLink, err
}
-// PinChatMessage pin message in supergroup
-func (bot *BotAPI) PinChatMessage(config PinChatMessageConfig) (APIResponse, error) {
- v, err := config.values()
+// GetStickerSet returns a StickerSet.
+func (bot *BotAPI) GetStickerSet(config GetStickerSetConfig) (StickerSet, error) {
+ resp, err := bot.Request(config)
if err != nil {
- return APIResponse{}, err
+ return StickerSet{}, err
}
- bot.debugLog(config.method(), v, nil)
+ var stickers StickerSet
+ err = json.Unmarshal(resp.Result, &stickers)
- return bot.MakeRequest(config.method(), v)
+ return stickers, err
}
-// UnpinChatMessage unpin message in supergroup
-func (bot *BotAPI) UnpinChatMessage(config UnpinChatMessageConfig) (APIResponse, error) {
- v, err := config.values()
+// StopPoll stops a poll and returns the result.
+func (bot *BotAPI) StopPoll(config StopPollConfig) (Poll, error) {
+ resp, err := bot.Request(config)
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
}
-// SetChatTitle change title of chat.
-func (bot *BotAPI) SetChatTitle(config SetChatTitleConfig) (APIResponse, error) {
- v, err := config.values()
- if err != nil {
- return APIResponse{}, err
- }
-
- bot.debugLog(config.method(), v, nil)
-
- return bot.MakeRequest(config.method(), v)
+// GetMyCommands gets the currently registered commands.
+func (bot *BotAPI) GetMyCommands() ([]BotCommand, error) {
+ return bot.GetMyCommandsWithConfig(GetMyCommandsConfig{})
}
-// SetChatDescription change description of chat.
-func (bot *BotAPI) SetChatDescription(config SetChatDescriptionConfig) (APIResponse, error) {
- v, err := config.values()
+// GetMyCommandsWithConfig gets the currently registered commands with a config.
+func (bot *BotAPI) GetMyCommandsWithConfig(config GetMyCommandsConfig) ([]BotCommand, error) {
+ resp, err := bot.Request(config)
if err != nil {
- return APIResponse{}, err
+ return nil, err
}
- bot.debugLog(config.method(), v, nil)
+ var commands []BotCommand
+ err = json.Unmarshal(resp.Result, &commands)
- return bot.MakeRequest(config.method(), v)
+ return commands, err
}
-// SetChatPhoto change photo of chat.
-func (bot *BotAPI) SetChatPhoto(config SetChatPhotoConfig) (APIResponse, error) {
+// CopyMessage copy messages of any kind. The method is analogous to the method
+// forwardMessage, but the copied message doesn't have a link to the original
+// message. Returns the MessageID of the sent message on success.
+func (bot *BotAPI) CopyMessage(config CopyMessageConfig) (MessageID, error) {
params, err := config.params()
if err != nil {
- return APIResponse{}, err
+ return MessageID{}, 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 MessageID{}, err
}
- bot.debugLog(config.method(), v, nil)
-
- return bot.MakeRequest(config.method(), v)
-}
-
-// GetStickerSet get a sticker set.
-func (bot *BotAPI) GetStickerSet(config GetStickerSetConfig) (StickerSet, error) {
- v, err := config.values()
- if err != nil {
- return StickerSet{}, err
- }
- bot.debugLog(config.method(), v, nil)
- res, err := bot.MakeRequest(config.method(), v)
- if err != nil {
- return StickerSet{}, err
- }
- stickerSet := StickerSet{}
- err = json.Unmarshal(res.Result, &stickerSet)
- if err != nil {
- return StickerSet{}, err
- }
- return stickerSet, nil
-}
+ var messageID MessageID
+ err = json.Unmarshal(resp.Result, &messageID)
-// GetMyCommands gets the current list of the bot's commands.
-func (bot *BotAPI) GetMyCommands() ([]BotCommand, error) {
- res, err := bot.MakeRequest("getMyCommands", nil)
- if err != nil {
- return nil, err
- }
- var commands []BotCommand
- err = json.Unmarshal(res.Result, &commands)
- if err != nil {
- return nil, err
- }
- return commands, nil
-}
-
-// SetMyCommands changes the list of the bot's commands.
-func (bot *BotAPI) SetMyCommands(commands []BotCommand) error {
- v := url.Values{}
- data, err := json.Marshal(commands)
- if err != nil {
- return err
- }
- v.Add("commands", string(data))
- _, err = bot.MakeRequest("setMyCommands", v)
- if err != nil {
- return err
- }
- return nil
+ return messageID, err
}
// EscapeText takes an input text and escape Telegram markup symbols.
diff --git a/bot_test.go b/bot_test.go
index 619b4e30..cd796a64 100644
--- a/bot_test.go
+++ b/bot_test.go
@@ -1,22 +1,20 @@
-package tgbotapi_test
+package tgbotapi
import (
"io/ioutil"
- "log"
"net/http"
"os"
"testing"
"time"
-
- tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
)
const (
TestToken = "153667468:AAHlSHlMqSt1f_uFmVRJbm5gntu2HI4WW8I"
ChatID = 76918703
+ Channel = "@tgbotapitest"
SupergroupChatID = -1001120141283
ReplyToMessageID = 35
- ExistingPhotoFileID = "AgADAgADw6cxG4zHKAkr42N7RwEN3IFShCoABHQwXEtVks4EH2wBAAEC"
+ ExistingPhotoFileID = "AgACAgIAAxkDAAEBFUZhIALQ9pZN4BUe8ZSzUU_2foSo1AACnrMxG0BucEhezsBWOgcikQEAAwIAA20AAyAE"
ExistingDocumentFileID = "BQADAgADOQADjMcoCcioX1GrDvp3Ag"
ExistingAudioFileID = "BQADAgADRgADjMcoCdXg3lSIN49lAg"
ExistingVoiceFileID = "AwADAgADWQADjMcoCeul6r_q52IyAg"
@@ -25,88 +23,116 @@ const (
ExistingStickerFileID = "BQADAgADcwADjMcoCbdl-6eB--YPAg"
)
-func getBot(t *testing.T) (*tgbotapi.BotAPI, error) {
- bot, err := tgbotapi.NewBotAPI(TestToken)
+type testLogger struct {
+ t *testing.T
+}
+
+func (t testLogger) Println(v ...interface{}) {
+ t.t.Log(v...)
+}
+
+func (t testLogger) Printf(format string, v ...interface{}) {
+ t.t.Logf(format, v...)
+}
+
+func getBot(t *testing.T) (*BotAPI, error) {
+ bot, err := NewBotAPI(TestToken)
bot.Debug = true
+ logger := testLogger{t}
+ SetLogger(logger)
+
if err != nil {
t.Error(err)
- t.Fail()
}
return bot, err
}
func TestNewBotAPI_notoken(t *testing.T) {
- _, err := tgbotapi.NewBotAPI("")
+ _, err := NewBotAPI("")
if err == nil {
t.Error(err)
- t.Fail()
}
}
func TestGetUpdates(t *testing.T) {
bot, _ := getBot(t)
- u := tgbotapi.NewUpdate(0)
+ u := NewUpdate(0)
_, err := bot.GetUpdates(u)
if err != nil {
t.Error(err)
- t.Fail()
}
}
func TestSendWithMessage(t *testing.T) {
bot, _ := getBot(t)
- msg := tgbotapi.NewMessage(ChatID, "A test message from the test library in telegram-bot-api")
- msg.ParseMode = "markdown"
+ msg := NewMessage(ChatID, "A test message from the test library in telegram-bot-api")
+ msg.ParseMode = ModeMarkdown
_, err := bot.Send(msg)
if err != nil {
t.Error(err)
- t.Fail()
}
}
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)
if err != nil {
t.Error(err)
- t.Fail()
}
}
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 {
t.Error(err)
- t.Fail()
+ }
+}
+
+func TestCopyMessage(t *testing.T) {
+ bot, _ := getBot(t)
+
+ msg := NewMessage(ChatID, "A test message from the test library in telegram-bot-api")
+ message, err := bot.Send(msg)
+ if err != nil {
+ t.Error(err)
+ }
+
+ copyMessageConfig := NewCopyMessage(SupergroupChatID, message.Chat.ID, message.MessageID)
+ messageID, err := bot.CopyMessage(copyMessageConfig)
+ if err != nil {
+ t.Error(err)
+ }
+
+ if messageID.MessageID == message.MessageID {
+ t.Error("copied message ID was the same as original message")
}
}
func TestSendWithNewPhoto(t *testing.T) {
bot, _ := getBot(t)
- msg := tgbotapi.NewPhotoUpload(ChatID, "tests/image.jpg")
+ msg := NewPhoto(ChatID, FilePath("tests/image.jpg"))
msg.Caption = "Test"
_, err := bot.Send(msg)
if err != nil {
t.Error(err)
- t.Fail()
}
}
@@ -114,15 +140,14 @@ 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 := NewPhoto(ChatID, b)
msg.Caption = "Test"
_, err := bot.Send(msg)
if err != nil {
t.Error(err)
- t.Fail()
}
}
@@ -130,36 +155,50 @@ 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}
- msg := tgbotapi.NewPhotoUpload(ChatID, reader)
+ msg := NewPhoto(ChatID, reader)
msg.Caption = "Test"
_, err := bot.Send(msg)
if err != nil {
t.Error(err)
- t.Fail()
}
}
func TestSendWithNewPhotoReply(t *testing.T) {
bot, _ := getBot(t)
- msg := tgbotapi.NewPhotoUpload(ChatID, "tests/image.jpg")
+ msg := NewPhoto(ChatID, FilePath("tests/image.jpg"))
msg.ReplyToMessageID = ReplyToMessageID
_, err := bot.Send(msg)
+ if err != nil {
+ t.Error(err)
+ }
+}
+
+func TestSendNewPhotoToChannel(t *testing.T) {
+ bot, _ := getBot(t)
+
+ msg := NewPhotoToChannel(Channel, FilePath("tests/image.jpg"))
+ msg.Caption = "Test"
+ _, err := bot.Send(msg)
+
if err != nil {
t.Error(err)
t.Fail()
}
}
-func TestSendWithExistingPhoto(t *testing.T) {
+func TestSendNewPhotoToChannelFileBytes(t *testing.T) {
bot, _ := getBot(t)
- msg := tgbotapi.NewPhotoShare(ChatID, ExistingPhotoFileID)
+ data, _ := ioutil.ReadFile("tests/image.jpg")
+ b := FileBytes{Name: "image.jpg", Bytes: data}
+
+ msg := NewPhotoToChannel(Channel, b)
msg.Caption = "Test"
_, err := bot.Send(msg)
@@ -169,10 +208,14 @@ func TestSendWithExistingPhoto(t *testing.T) {
}
}
-func TestSendWithNewDocument(t *testing.T) {
+func TestSendNewPhotoToChannelFileReader(t *testing.T) {
bot, _ := getBot(t)
- msg := tgbotapi.NewDocumentUpload(ChatID, "tests/image.jpg")
+ f, _ := os.Open("tests/image.jpg")
+ reader := FileReader{Name: "image.jpg", Reader: f}
+
+ msg := NewPhotoToChannel(Channel, reader)
+ msg.Caption = "Test"
_, err := bot.Send(msg)
if err != nil {
@@ -181,39 +224,70 @@ func TestSendWithNewDocument(t *testing.T) {
}
}
+func TestSendWithExistingPhoto(t *testing.T) {
+ bot, _ := getBot(t)
+
+ msg := NewPhoto(ChatID, FileID(ExistingPhotoFileID))
+ msg.Caption = "Test"
+ _, err := bot.Send(msg)
+
+ if err != nil {
+ t.Error(err)
+ }
+}
+
+func TestSendWithNewDocument(t *testing.T) {
+ bot, _ := getBot(t)
+
+ msg := NewDocument(ChatID, FilePath("tests/image.jpg"))
+ _, err := bot.Send(msg)
+
+ if err != nil {
+ t.Error(err)
+ }
+}
+
+func TestSendWithNewDocumentAndThumb(t *testing.T) {
+ bot, _ := getBot(t)
+
+ msg := NewDocument(ChatID, FilePath("tests/voice.ogg"))
+ msg.Thumb = FilePath("tests/image.jpg")
+ _, err := bot.Send(msg)
+
+ if err != nil {
+ t.Error(err)
+ }
+}
+
func TestSendWithExistingDocument(t *testing.T) {
bot, _ := getBot(t)
- msg := tgbotapi.NewDocumentShare(ChatID, ExistingDocumentFileID)
+ msg := NewDocument(ChatID, FileID(ExistingDocumentFileID))
_, err := bot.Send(msg)
if err != nil {
t.Error(err)
- t.Fail()
}
}
func TestSendWithNewAudio(t *testing.T) {
bot, _ := getBot(t)
- msg := tgbotapi.NewAudioUpload(ChatID, "tests/audio.mp3")
+ msg := NewAudio(ChatID, FilePath("tests/audio.mp3"))
msg.Title = "TEST"
msg.Duration = 10
msg.Performer = "TEST"
- msg.MimeType = "audio/mpeg"
- msg.FileSize = 688
_, err := bot.Send(msg)
if err != nil {
t.Error(err)
- t.Fail()
}
}
func TestSendWithExistingAudio(t *testing.T) {
bot, _ := getBot(t)
- msg := tgbotapi.NewAudioShare(ChatID, ExistingAudioFileID)
+ msg := NewAudio(ChatID, FileID(ExistingAudioFileID))
msg.Title = "TEST"
msg.Duration = 10
msg.Performer = "TEST"
@@ -222,73 +296,67 @@ func TestSendWithExistingAudio(t *testing.T) {
if err != nil {
t.Error(err)
- t.Fail()
}
}
func TestSendWithNewVoice(t *testing.T) {
bot, _ := getBot(t)
- msg := tgbotapi.NewVoiceUpload(ChatID, "tests/voice.ogg")
+ msg := NewVoice(ChatID, FilePath("tests/voice.ogg"))
msg.Duration = 10
_, err := bot.Send(msg)
if err != nil {
t.Error(err)
- t.Fail()
}
}
func TestSendWithExistingVoice(t *testing.T) {
bot, _ := getBot(t)
- msg := tgbotapi.NewVoiceShare(ChatID, ExistingVoiceFileID)
+ msg := NewVoice(ChatID, FileID(ExistingVoiceFileID))
msg.Duration = 10
_, err := bot.Send(msg)
if err != nil {
t.Error(err)
- t.Fail()
}
}
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)
- t.Fail()
}
}
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)
- t.Fail()
}
}
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)
- t.Fail()
}
}
func TestSendWithNewVideo(t *testing.T) {
bot, _ := getBot(t)
- msg := tgbotapi.NewVideoUpload(ChatID, "tests/video.mp4")
+ msg := NewVideo(ChatID, FilePath("tests/video.mp4"))
msg.Duration = 10
msg.Caption = "TEST"
@@ -296,14 +364,13 @@ func TestSendWithNewVideo(t *testing.T) {
if err != nil {
t.Error(err)
- t.Fail()
}
}
func TestSendWithExistingVideo(t *testing.T) {
bot, _ := getBot(t)
- msg := tgbotapi.NewVideoShare(ChatID, ExistingVideoFileID)
+ msg := NewVideo(ChatID, FileID(ExistingVideoFileID))
msg.Duration = 10
msg.Caption = "TEST"
@@ -311,69 +378,64 @@ func TestSendWithExistingVideo(t *testing.T) {
if err != nil {
t.Error(err)
- t.Fail()
}
}
func TestSendWithNewVideoNote(t *testing.T) {
bot, _ := getBot(t)
- msg := tgbotapi.NewVideoNoteUpload(ChatID, 240, "tests/videonote.mp4")
+ msg := NewVideoNote(ChatID, 240, FilePath("tests/videonote.mp4"))
msg.Duration = 10
_, err := bot.Send(msg)
if err != nil {
t.Error(err)
- t.Fail()
}
}
func TestSendWithExistingVideoNote(t *testing.T) {
bot, _ := getBot(t)
- msg := tgbotapi.NewVideoNoteShare(ChatID, 240, ExistingVideoNoteFileID)
+ msg := NewVideoNote(ChatID, 240, FileID(ExistingVideoNoteFileID))
msg.Duration = 10
_, err := bot.Send(msg)
if err != nil {
t.Error(err)
- t.Fail()
}
}
func TestSendWithNewSticker(t *testing.T) {
bot, _ := getBot(t)
- msg := tgbotapi.NewStickerUpload(ChatID, "tests/image.jpg")
+ msg := NewSticker(ChatID, FilePath("tests/image.jpg"))
_, err := bot.Send(msg)
if err != nil {
t.Error(err)
- t.Fail()
}
}
func TestSendWithExistingSticker(t *testing.T) {
bot, _ := getBot(t)
- msg := tgbotapi.NewStickerShare(ChatID, ExistingStickerFileID)
+ msg := NewSticker(ChatID, FileID(ExistingStickerFileID))
_, err := bot.Send(msg)
if err != nil {
t.Error(err)
- t.Fail()
}
}
func TestSendWithNewStickerAndKeyboardHide(t *testing.T) {
bot, _ := getBot(t)
- msg := tgbotapi.NewStickerUpload(ChatID, "tests/image.jpg")
- msg.ReplyMarkup = tgbotapi.ReplyKeyboardRemove{
+ msg := NewSticker(ChatID, FilePath("tests/image.jpg"))
+ msg.ReplyMarkup = ReplyKeyboardRemove{
RemoveKeyboard: true,
Selective: false,
}
@@ -381,15 +443,14 @@ func TestSendWithNewStickerAndKeyboardHide(t *testing.T) {
if err != nil {
t.Error(err)
- t.Fail()
}
}
func TestSendWithExistingStickerAndKeyboardHide(t *testing.T) {
bot, _ := getBot(t)
- msg := tgbotapi.NewStickerShare(ChatID, ExistingStickerFileID)
- msg.ReplyMarkup = tgbotapi.ReplyKeyboardRemove{
+ msg := NewSticker(ChatID, FileID(ExistingStickerFileID))
+ msg.ReplyMarkup = ReplyKeyboardRemove{
RemoveKeyboard: true,
Selective: false,
}
@@ -398,14 +459,13 @@ func TestSendWithExistingStickerAndKeyboardHide(t *testing.T) {
if err != nil {
t.Error(err)
- t.Fail()
}
}
func TestSendWithDice(t *testing.T) {
bot, _ := getBot(t)
- msg := tgbotapi.NewDice(ChatID)
+ msg := NewDice(ChatID)
_, err := bot.Send(msg)
if err != nil {
@@ -418,7 +478,7 @@ func TestSendWithDice(t *testing.T) {
func TestSendWithDiceWithEmoji(t *testing.T) {
bot, _ := getBot(t)
- msg := tgbotapi.NewDiceWithEmoji(ChatID, "🏀")
+ msg := NewDiceWithEmoji(ChatID, "🏀")
_, err := bot.Send(msg)
if err != nil {
@@ -431,38 +491,37 @@ func TestSendWithDiceWithEmoji(t *testing.T) {
func TestGetFile(t *testing.T) {
bot, _ := getBot(t)
- file := tgbotapi.FileConfig{FileID: ExistingPhotoFileID}
+ file := FileConfig{
+ FileID: ExistingPhotoFileID,
+ }
_, err := bot.GetFile(file)
if err != nil {
t.Error(err)
- t.Fail()
}
}
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)
- t.Fail()
}
}
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,
},
@@ -472,17 +531,15 @@ func TestSendEditMessage(t *testing.T) {
_, err = bot.Send(edit)
if err != nil {
t.Error(err)
- t.Fail()
}
}
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()
}
}
@@ -491,19 +548,26 @@ func TestSetWebhookWithCert(t *testing.T) {
time.Sleep(time.Second * 2)
- bot.RemoveWebhook()
+ bot.Request(DeleteWebhookConfig{})
+
+ wh, err := NewWebhookWithCert("https://example.com/tgbotapi-test/"+bot.Token, FilePath("tests/cert.pem"))
- wh := tgbotapi.NewWebhookWithCert("https://example.com/tgbotapi-test/"+bot.Token, "tests/cert.pem")
- _, err := bot.SetWebhook(wh)
if err != nil {
t.Error(err)
- t.Fail()
}
+ _, err = bot.Request(wh)
+
+ if err != nil {
+ t.Error(err)
+ }
+
_, err = bot.GetWebhookInfo()
+
if err != nil {
t.Error(err)
}
- bot.RemoveWebhook()
+
+ bot.Request(DeleteWebhookConfig{})
}
func TestSetWebhookWithoutCert(t *testing.T) {
@@ -511,15 +575,22 @@ func TestSetWebhookWithoutCert(t *testing.T) {
time.Sleep(time.Second * 2)
- bot.RemoveWebhook()
+ bot.Request(DeleteWebhookConfig{})
+
+ wh, err := NewWebhook("https://example.com/tgbotapi-test/" + bot.Token)
+
+ if err != nil {
+ t.Error(err)
+ }
+
+ _, err = bot.Request(wh)
- wh := tgbotapi.NewWebhook("https://example.com/tgbotapi-test/" + bot.Token)
- _, err := bot.SetWebhook(wh)
if err != nil {
t.Error(err)
- t.Fail()
}
+
info, err := bot.GetWebhookInfo()
+
if err != nil {
t.Error(err)
}
@@ -527,51 +598,93 @@ func TestSetWebhookWithoutCert(t *testing.T) {
t.Errorf("Expected maximum connections to be greater than 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(DeleteWebhookConfig{})
}
-func TestUpdatesChan(t *testing.T) {
+func TestSendWithMediaGroupPhotoVideo(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(FileURL("https://github.com/go-telegram-bot-api/telegram-bot-api/raw/0a3a1c8716c4cd8d26a262af9f12dcbab7f3f28c/tests/image.jpg")),
+ NewInputMediaPhoto(FilePath("tests/image.jpg")),
+ NewInputMediaVideo(FilePath("tests/video.mp4")),
+ })
+ messages, err := bot.SendMediaGroup(cfg)
if err != nil {
t.Error(err)
- t.Fail()
+ }
+
+ if messages == nil {
+ t.Error("No received messages")
+ }
+
+ if len(messages) != len(cfg.Media) {
+ t.Errorf("Different number of messages: %d", len(messages))
}
}
-func TestSendWithMediaGroup(t *testing.T) {
+func TestSendWithMediaGroupDocument(t *testing.T) {
bot, _ := getBot(t)
- cfg := tgbotapi.NewMediaGroup(ChatID, []interface{}{
- tgbotapi.NewInputMediaPhoto("https://github.com/go-telegram-bot-api/telegram-bot-api/raw/0a3a1c8716c4cd8d26a262af9f12dcbab7f3f28c/tests/image.jpg"),
- tgbotapi.NewInputMediaVideo("https://github.com/go-telegram-bot-api/telegram-bot-api/raw/0a3a1c8716c4cd8d26a262af9f12dcbab7f3f28c/tests/video.mp4"),
+ cfg := NewMediaGroup(ChatID, []interface{}{
+ NewInputMediaDocument(FileURL("https://i.imgur.com/unQLJIb.jpg")),
+ NewInputMediaDocument(FilePath("tests/image.jpg")),
})
- _, err := bot.Send(cfg)
+
+ messages, err := bot.SendMediaGroup(cfg)
if err != nil {
t.Error(err)
}
+
+ if messages == nil {
+ t.Error("No received messages")
+ }
+
+ if len(messages) != len(cfg.Media) {
+ t.Errorf("Different number of messages: %d", len(messages))
+ }
+}
+
+func TestSendWithMediaGroupAudio(t *testing.T) {
+ bot, _ := getBot(t)
+
+ cfg := NewMediaGroup(ChatID, []interface{}{
+ NewInputMediaAudio(FilePath("tests/audio.mp3")),
+ NewInputMediaAudio(FilePath("tests/audio.mp3")),
+ })
+
+ messages, err := bot.SendMediaGroup(cfg)
+ if err != nil {
+ t.Error(err)
+ }
+
+ if messages == nil {
+ t.Error("No received messages")
+ }
+
+ if len(messages) != len(cfg.Media) {
+ t.Errorf("Different number of messages: %d", len(messages))
+ }
}
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
@@ -585,7 +698,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)
@@ -593,26 +706,37 @@ 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"))
+ wh, err := NewWebhookWithCert("https://www.google.com:8443/"+bot.Token, FilePath("cert.pem"))
+
+ if err != nil {
+ panic(err)
+ }
+
+ _, err = bot.Request(wh)
+
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)
@@ -622,22 +746,28 @@ func ExampleNewWebhook() {
}
func ExampleWebhookHandler() {
- 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"))
+ wh, err := NewWebhookWithCert("https://www.google.com:8443/"+bot.Token, FilePath("cert.pem"))
+
+ if err != nil {
+ panic(err)
+ }
+
+ _, err = bot.Request(wh)
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)
@@ -655,35 +785,35 @@ func ExampleWebhookHandler() {
go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil)
}
-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)
}
}
@@ -692,64 +822,229 @@ 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.ParseMode = "markdown"
+ msg := NewMessage(ChatID, "A test message from the test library in telegram-bot-api")
+ msg.ParseMode = ModeMarkdown
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)
- t.Fail()
}
}
func TestPinChatMessage(t *testing.T) {
bot, _ := getBot(t)
- msg := tgbotapi.NewMessage(SupergroupChatID, "A test message from the test library in telegram-bot-api")
- msg.ParseMode = "markdown"
+ msg := NewMessage(SupergroupChatID, "A test message from the test library in telegram-bot-api")
+ msg.ParseMode = ModeMarkdown
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)
- t.Fail()
}
}
func TestUnpinChatMessage(t *testing.T) {
bot, _ := getBot(t)
- msg := tgbotapi.NewMessage(SupergroupChatID, "A test message from the test library in telegram-bot-api")
- msg.ParseMode = "markdown"
+ msg := NewMessage(SupergroupChatID, "A test message from the test library in telegram-bot-api")
+ msg.ParseMode = ModeMarkdown
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)
+ }
+
+ unpinChatMessageConfig := UnpinChatMessageConfig{
+ ChatID: message.Chat.ID,
+ MessageID: message.MessageID,
+ }
+
+ if _, err := bot.Request(unpinChatMessageConfig); err != nil {
+ t.Error(err)
+ }
+}
+
+func TestUnpinAllChatMessages(t *testing.T) {
+ bot, _ := getBot(t)
+
+ msg := NewMessage(SupergroupChatID, "A test message from the test library in telegram-bot-api")
+ msg.ParseMode = ModeMarkdown
+ message, _ := bot.Send(msg)
+
+ pinChatMessageConfig := PinChatMessageConfig{
+ ChatID: message.Chat.ID,
+ MessageID: message.MessageID,
+ DisableNotification: true,
+ }
+
+ if _, err := bot.Request(pinChatMessageConfig); err != nil {
+ t.Error(err)
+ }
+
+ unpinAllChatMessagesConfig := UnpinAllChatMessagesConfig{
ChatID: message.Chat.ID,
}
- _, err = bot.UnpinChatMessage(unpinChatMessageConfig)
+ if _, err := bot.Request(unpinAllChatMessagesConfig); err != nil {
+ t.Error(err)
+ }
+}
+
+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)
+ }
+
+ if result.Question != "Are polls working?" {
+ t.Error("Poll question did not match")
+ }
+
+ if !result.IsClosed {
+ t.Error("Poll did not end")
+ }
+
+ 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")
+ }
+}
+
+func TestSendDice(t *testing.T) {
+ bot, _ := getBot(t)
+
+ dice := NewSendDice(ChatID)
+
+ msg, err := bot.Send(dice)
+ if err != nil {
+ t.Error("Unable to send dice roll")
+ }
+
+ if msg.Dice == nil {
+ t.Error("Dice roll was not received")
+ }
+}
+
+func TestCommands(t *testing.T) {
+ bot, _ := getBot(t)
+
+ setCommands := NewSetMyCommands(BotCommand{
+ Command: "test",
+ Description: "a test command",
+ })
+
+ if _, err := bot.Request(setCommands); err != nil {
+ t.Error("Unable to set commands")
+ }
+
+ commands, err := bot.GetMyCommands()
+ if err != nil {
+ t.Error("Unable to get commands")
+ }
+
+ if len(commands) != 1 {
+ t.Error("Incorrect number of commands returned")
+ }
+
+ if commands[0].Command != "test" || commands[0].Description != "a test command" {
+ t.Error("Commands were incorrectly set")
+ }
+
+ setCommands = NewSetMyCommandsWithScope(NewBotCommandScopeAllPrivateChats(), BotCommand{
+ Command: "private",
+ Description: "a private command",
+ })
+
+ if _, err := bot.Request(setCommands); err != nil {
+ t.Error("Unable to set commands")
+ }
+
+ commands, err = bot.GetMyCommandsWithConfig(NewGetMyCommandsWithScope(NewBotCommandScopeAllPrivateChats()))
+ if err != nil {
+ t.Error("Unable to get commands")
+ }
+
+ if len(commands) != 1 {
+ t.Error("Incorrect number of commands returned")
+ }
+
+ if commands[0].Command != "private" || commands[0].Description != "a private command" {
+ t.Error("Commands were incorrectly set")
+ }
+}
+
+// TODO: figure out why test is failing
+//
+// func TestEditMessageMedia(t *testing.T) {
+// bot, _ := getBot(t)
+
+// msg := NewPhoto(ChatID, "tests/image.jpg")
+// msg.Caption = "Test"
+// m, err := bot.Send(msg)
+
+// if err != nil {
+// t.Error(err)
+// }
+
+// edit := EditMessageMediaConfig{
+// BaseEdit: BaseEdit{
+// ChatID: ChatID,
+// MessageID: m.MessageID,
+// },
+// Media: NewInputMediaVideo(FilePath("tests/video.mp4")),
+// }
+
+// _, err = bot.Request(edit)
+// if err != nil {
+// t.Error(err)
+// }
+// }
+
+func TestPrepareInputMediaForParams(t *testing.T) {
+ media := []interface{}{
+ NewInputMediaPhoto(FilePath("tests/image.jpg")),
+ NewInputMediaVideo(FileID("test")),
+ }
+
+ prepared := prepareInputMediaForParams(media)
+
+ if media[0].(InputMediaPhoto).Media != FilePath("tests/image.jpg") {
+ t.Error("Original media was changed")
+ }
+
+ if prepared[0].(InputMediaPhoto).Media != fileAttach("attach://file-0") {
+ t.Error("New media was not replaced")
+ }
+
+ if prepared[1].(InputMediaVideo).Media != FileID("test") {
+ t.Error("Passthrough value was not the same")
}
}
diff --git a/configs.go b/configs.go
index 98abacea..3730a6d1 100644
--- a/configs.go
+++ b/configs.go
@@ -1,9 +1,11 @@
package tgbotapi
import (
- "encoding/json"
+ "bytes"
+ "fmt"
"io"
"net/url"
+ "os"
"strconv"
)
@@ -18,14 +20,21 @@ const (
// Constant values for ChatActions
const (
- ChatTyping = "typing"
- ChatUploadPhoto = "upload_photo"
- ChatRecordVideo = "record_video"
- ChatUploadVideo = "upload_video"
- ChatRecordAudio = "record_audio"
- ChatUploadAudio = "upload_audio"
- ChatUploadDocument = "upload_document"
- ChatFindLocation = "find_location"
+ ChatTyping = "typing"
+ ChatUploadPhoto = "upload_photo"
+ ChatRecordVideo = "record_video"
+ ChatUploadVideo = "upload_video"
+ ChatRecordVoice = "record_voice"
+ ChatUploadVoice = "upload_voice"
+ // Deprecated: use ChatRecordVoice instead.
+ ChatRecordAudio = "record_audio"
+ // Deprecated: use ChatUploadVoice instead.
+ ChatUploadAudio = "upload_audio"
+ ChatUploadDocument = "upload_document"
+ ChatChooseSticker = "choose_sticker"
+ ChatFindLocation = "find_location"
+ ChatRecordVideoNote = "record_video_note"
+ ChatUploadVideoNote = "upload_video_note"
)
// API errors
@@ -41,130 +50,253 @@ const (
ModeHTML = "HTML"
)
+// Constant values for update types
+const (
+ // New incoming message of any kind — text, photo, sticker, etc.
+ UpdateTypeMessage = "message"
+
+ // New version of a message that is known to the bot and was edited
+ UpdateTypeEditedMessage = "edited_message"
+
+ // New incoming channel post of any kind — text, photo, sticker, etc.
+ UpdateTypeChannelPost = "channel_post"
+
+ // New version of a channel post that is known to the bot and was edited
+ UpdateTypeEditedChannelPost = "edited_channel_post"
+
+ // New incoming inline query
+ UpdateTypeInlineQuery = "inline_query"
+
+ // The result of an inline query that was chosen by a user and sent to their
+ // chat partner. Please see the documentation on the feedback collecting for
+ // details on how to enable these updates for your bot.
+ UpdateTypeChosenInlineResult = "chosen_inline_result"
+
+ // New incoming callback query
+ UpdateTypeCallbackQuery = "callback_query"
+
+ // New incoming shipping query. Only for invoices with flexible price
+ UpdateTypeShippingQuery = "shipping_query"
+
+ // New incoming pre-checkout query. Contains full information about checkout
+ UpdateTypePreCheckoutQuery = "pre_checkout_query"
+
+ // New poll state. Bots receive only updates about stopped polls and polls
+ // which are sent by the bot
+ UpdateTypePoll = "poll"
+
+ // A user changed their answer in a non-anonymous poll. Bots receive new votes
+ // only in polls that were sent by the bot itself.
+ UpdateTypePollAnswer = "poll_answer"
+
+ // The bot's chat member status was updated in a chat. For private chats, this
+ // update is received only when the bot is blocked or unblocked by the user.
+ UpdateTypeMyChatMember = "my_chat_member"
+
+ // The bot must be an administrator in the chat and must explicitly specify
+ // this update in the list of allowed_updates to receive these updates.
+ UpdateTypeChatMember = "chat_member"
+)
+
// Library errors
const (
- // ErrBadFileType happens when you pass an unknown type
- ErrBadFileType = "bad file type"
- ErrBadURL = "bad or empty url"
+ ErrBadURL = "bad or empty url"
)
// Chattable is any config type that can be sent.
type Chattable interface {
- values() (url.Values, error)
+ params() (Params, error)
method() string
}
// Fileable is any config type that can be sent that includes a file.
type Fileable interface {
Chattable
- params() (map[string]string, error)
- name() string
- getFile() interface{}
- useExistingFile() bool
+ files() []RequestFile
}
-// BaseChat is base type for all chat config types.
-type BaseChat struct {
- ChatID int64 // required
- ChannelUsername string
- ReplyToMessageID int
- ReplyMarkup interface{}
- DisableNotification bool
+// RequestFile represents a file associated with a field name.
+type RequestFile struct {
+ // The file field name.
+ Name string
+ // The file data to include.
+ Data RequestFileData
}
-func (chat *BaseChat) params() (Params, error) {
- params := make(Params)
+// RequestFileData represents the data to be used for a file.
+type RequestFileData interface {
+ // If the file needs to be uploaded.
+ NeedsUpload() bool
- params.AddFirstValid("chat_id", chat.ChatID, chat.ChannelUsername)
- params.AddNonZero("reply_to_message_id", chat.ReplyToMessageID)
- params.AddBool("disable_notification", chat.DisableNotification)
+ // Get the file name and an `io.Reader` for the file to be uploaded. This
+ // must only be called when the file needs to be uploaded.
+ UploadData() (string, io.Reader, error)
+ // Get the file data to send when a file does not need to be uploaded. This
+ // must only be called when the file does not need to be uploaded.
+ SendData() string
+}
- err := params.AddInterface("reply_markup", chat.ReplyMarkup)
+// FileBytes contains information about a set of bytes to upload
+// as a File.
+type FileBytes struct {
+ Name string
+ Bytes []byte
+}
- return params, err
+func (fb FileBytes) NeedsUpload() bool {
+ return true
}
-// values returns url.Values representation of BaseChat
-func (chat *BaseChat) values() (url.Values, error) {
- v := url.Values{}
- if chat.ChannelUsername != "" {
- v.Add("chat_id", chat.ChannelUsername)
- } else {
- v.Add("chat_id", strconv.FormatInt(chat.ChatID, 10))
- }
+func (fb FileBytes) UploadData() (string, io.Reader, error) {
+ return fb.Name, bytes.NewReader(fb.Bytes), nil
+}
- if chat.ReplyToMessageID != 0 {
- v.Add("reply_to_message_id", strconv.Itoa(chat.ReplyToMessageID))
- }
+func (fb FileBytes) SendData() string {
+ panic("FileBytes must be uploaded")
+}
- if chat.ReplyMarkup != nil {
- data, err := json.Marshal(chat.ReplyMarkup)
- if err != nil {
- return v, err
- }
+// FileReader contains information about a reader to upload as a File.
+type FileReader struct {
+ Name string
+ Reader io.Reader
+}
+
+func (fr FileReader) NeedsUpload() bool {
+ return true
+}
- v.Add("reply_markup", string(data))
+func (fr FileReader) UploadData() (string, io.Reader, error) {
+ return fr.Name, fr.Reader, nil
+}
+
+func (fr FileReader) SendData() string {
+ panic("FileReader must be uploaded")
+}
+
+// FilePath is a path to a local file.
+type FilePath string
+
+func (fp FilePath) NeedsUpload() bool {
+ return true
+}
+
+func (fp FilePath) UploadData() (string, io.Reader, error) {
+ fileHandle, err := os.Open(string(fp))
+ if err != nil {
+ return "", nil, err
}
- v.Add("disable_notification", strconv.FormatBool(chat.DisableNotification))
+ name := fileHandle.Name()
+ return name, fileHandle, err
+}
- return v, nil
+func (fp FilePath) SendData() string {
+ panic("FilePath must be uploaded")
}
-// BaseFile is a base type for all file config types.
-type BaseFile struct {
- BaseChat
- File interface{}
- FileID string
- UseExisting bool
- MimeType string
- FileSize int
+// FileURL is a URL to use as a file for a request.
+type FileURL string
+
+func (fu FileURL) NeedsUpload() bool {
+ return false
}
-// params returns a map[string]string representation of BaseFile.
-func (file BaseFile) params() (map[string]string, error) {
- params := make(map[string]string)
+func (fu FileURL) UploadData() (string, io.Reader, error) {
+ panic("FileURL cannot be uploaded")
+}
- if file.ChannelUsername != "" {
- params["chat_id"] = file.ChannelUsername
- } else {
- params["chat_id"] = strconv.FormatInt(file.ChatID, 10)
- }
+func (fu FileURL) SendData() string {
+ return string(fu)
+}
- if file.ReplyToMessageID != 0 {
- params["reply_to_message_id"] = strconv.Itoa(file.ReplyToMessageID)
- }
+// FileID is an ID of a file already uploaded to Telegram.
+type FileID string
- if file.ReplyMarkup != nil {
- data, err := json.Marshal(file.ReplyMarkup)
- if err != nil {
- return params, err
- }
+func (fi FileID) NeedsUpload() bool {
+ return false
+}
- params["reply_markup"] = string(data)
- }
+func (fi FileID) UploadData() (string, io.Reader, error) {
+ panic("FileID cannot be uploaded")
+}
- if file.MimeType != "" {
- params["mime_type"] = file.MimeType
- }
+func (fi FileID) SendData() string {
+ return string(fi)
+}
- if file.FileSize > 0 {
- params["file_size"] = strconv.Itoa(file.FileSize)
- }
+// fileAttach is a internal file type used for processed media groups.
+type fileAttach string
- params["disable_notification"] = strconv.FormatBool(file.DisableNotification)
+func (fa fileAttach) NeedsUpload() bool {
+ return false
+}
- return params, nil
+func (fa fileAttach) UploadData() (string, io.Reader, error) {
+ panic("fileAttach cannot be uploaded")
+}
+
+func (fa fileAttach) SendData() string {
+ return string(fa)
+}
+
+// LogOutConfig is a request to log out of the cloud Bot API server.
+//
+// Note that you may not log back in for at least 10 minutes.
+type LogOutConfig struct{}
+
+func (LogOutConfig) method() string {
+ return "logOut"
+}
+
+func (LogOutConfig) params() (Params, error) {
+ return nil, nil
+}
+
+// CloseConfig is a request to close the bot instance on a local server.
+//
+// Note that you may not close an instance for the first 10 minutes after the
+// bot has started.
+type CloseConfig struct{}
+
+func (CloseConfig) method() string {
+ return "close"
+}
+
+func (CloseConfig) params() (Params, error) {
+ return nil, nil
+}
+
+// BaseChat is base type for all chat config types.
+type BaseChat struct {
+ ChatID int64 // required
+ ChannelUsername string
+ ReplyToMessageID int
+ ReplyMarkup interface{}
+ DisableNotification bool
+ AllowSendingWithoutReply bool
+}
+
+func (chat *BaseChat) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", chat.ChatID, chat.ChannelUsername)
+ params.AddNonZero("reply_to_message_id", chat.ReplyToMessageID)
+ params.AddBool("disable_notification", chat.DisableNotification)
+ params.AddBool("allow_sending_without_reply", chat.AllowSendingWithoutReply)
+
+ err := params.AddInterface("reply_markup", chat.ReplyMarkup)
+
+ return params, err
}
-// getFile returns the file.
-func (file BaseFile) getFile() interface{} {
- return file.File
+// BaseFile is a base type for all file config types.
+type BaseFile struct {
+ BaseChat
+ File RequestFileData
}
-// useExistingFile returns if the BaseFile has already been uploaded.
-func (file BaseFile) useExistingFile() bool {
- return file.UseExisting
+func (file BaseFile) params() (Params, error) {
+ return file.BaseChat.params()
}
// BaseEdit is base type of all chat edits.
@@ -176,29 +308,19 @@ type BaseEdit struct {
ReplyMarkup *InlineKeyboardMarkup
}
-func (edit BaseEdit) values() (url.Values, error) {
- v := url.Values{}
+func (edit BaseEdit) params() (Params, error) {
+ params := make(Params)
- if edit.InlineMessageID == "" {
- if edit.ChannelUsername != "" {
- v.Add("chat_id", edit.ChannelUsername)
- } else {
- v.Add("chat_id", strconv.FormatInt(edit.ChatID, 10))
- }
- v.Add("message_id", strconv.Itoa(edit.MessageID))
+ if edit.InlineMessageID != "" {
+ params["inline_message_id"] = edit.InlineMessageID
} else {
- v.Add("inline_message_id", edit.InlineMessageID)
+ params.AddFirstValid("chat_id", edit.ChatID, edit.ChannelUsername)
+ params.AddNonZero("message_id", edit.MessageID)
}
- if edit.ReplyMarkup != nil {
- data, err := json.Marshal(edit.ReplyMarkup)
- if err != nil {
- return v, err
- }
- v.Add("reply_markup", string(data))
- }
+ err := params.AddInterface("reply_markup", edit.ReplyMarkup)
- return v, nil
+ return params, err
}
// MessageConfig contains information about a SendMessage request.
@@ -206,25 +328,24 @@ type MessageConfig struct {
BaseChat
Text string
ParseMode string
+ Entities []MessageEntity
DisableWebPagePreview bool
}
-// values returns a url.Values representation of MessageConfig.
-func (config MessageConfig) values() (url.Values, error) {
- v, err := config.BaseChat.values()
+func (config MessageConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
if err != nil {
- return v, err
- }
- v.Add("text", config.Text)
- v.Add("disable_web_page_preview", strconv.FormatBool(config.DisableWebPagePreview))
- if config.ParseMode != "" {
- v.Add("parse_mode", config.ParseMode)
+ return params, err
}
- return v, nil
+ params.AddNonEmpty("text", config.Text)
+ params.AddBool("disable_web_page_preview", config.DisableWebPagePreview)
+ params.AddNonEmpty("parse_mode", config.ParseMode)
+ err = params.AddInterface("entities", config.Entities)
+
+ return params, err
}
-// method returns Telegram API method name for sending Message.
func (config MessageConfig) method() string {
return "sendMessage"
}
@@ -237,515 +358,466 @@ type ForwardConfig struct {
MessageID int // required
}
-// values returns a url.Values representation of ForwardConfig.
-func (config ForwardConfig) values() (url.Values, error) {
- v, err := config.BaseChat.values()
+func (config ForwardConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
if err != nil {
- return v, err
+ return params, err
}
- v.Add("from_chat_id", strconv.FormatInt(config.FromChatID, 10))
- v.Add("message_id", strconv.Itoa(config.MessageID))
- return v, nil
+
+ params.AddNonZero64("from_chat_id", config.FromChatID)
+ params.AddNonZero("message_id", config.MessageID)
+
+ return params, nil
}
-// method returns Telegram API method name for sending Forward.
func (config ForwardConfig) method() string {
return "forwardMessage"
}
-// PhotoConfig contains information about a SendPhoto request.
-type PhotoConfig struct {
- BaseFile
- Caption string
- ParseMode string
-}
-
-// Params returns a map[string]string representation of PhotoConfig.
-func (config PhotoConfig) params() (map[string]string, error) {
- params, _ := config.BaseFile.params()
-
- if config.Caption != "" {
- params["caption"] = config.Caption
- if config.ParseMode != "" {
- params["parse_mode"] = config.ParseMode
- }
- }
-
- return params, nil
+// CopyMessageConfig contains information about a copyMessage request.
+type CopyMessageConfig struct {
+ BaseChat
+ FromChatID int64
+ FromChannelUsername string
+ MessageID int
+ Caption string
+ ParseMode string
+ CaptionEntities []MessageEntity
}
-// Values returns a url.Values representation of PhotoConfig.
-func (config PhotoConfig) values() (url.Values, error) {
- v, err := config.BaseChat.values()
+func (config CopyMessageConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
if err != nil {
- return v, err
- }
-
- v.Add(config.name(), config.FileID)
- if config.Caption != "" {
- v.Add("caption", config.Caption)
- if config.ParseMode != "" {
- v.Add("parse_mode", config.ParseMode)
- }
+ return params, err
}
- return v, nil
-}
+ params.AddFirstValid("from_chat_id", config.FromChatID, config.FromChannelUsername)
+ params.AddNonZero("message_id", config.MessageID)
+ params.AddNonEmpty("caption", config.Caption)
+ params.AddNonEmpty("parse_mode", config.ParseMode)
+ err = params.AddInterface("caption_entities", config.CaptionEntities)
-// name returns the field name for the Photo.
-func (config PhotoConfig) name() string {
- return "photo"
+ return params, err
}
-// method returns Telegram API method name for sending Photo.
-func (config PhotoConfig) method() string {
- return "sendPhoto"
+func (config CopyMessageConfig) method() string {
+ return "copyMessage"
}
-// AudioConfig contains information about a SendAudio request.
-type AudioConfig struct {
+// PhotoConfig contains information about a SendPhoto request.
+type PhotoConfig struct {
BaseFile
- Caption string
- ParseMode string
- Duration int
- Performer string
- Title string
+ Thumb RequestFileData
+ Caption string
+ ParseMode string
+ CaptionEntities []MessageEntity
}
-// values returns a url.Values representation of AudioConfig.
-func (config AudioConfig) values() (url.Values, error) {
- v, err := config.BaseChat.values()
+func (config PhotoConfig) params() (Params, error) {
+ params, err := config.BaseFile.params()
if err != nil {
- return v, err
+ return params, err
}
- v.Add(config.name(), config.FileID)
- if config.Duration != 0 {
- v.Add("duration", strconv.Itoa(config.Duration))
- }
+ params.AddNonEmpty("caption", config.Caption)
+ params.AddNonEmpty("parse_mode", config.ParseMode)
+ err = params.AddInterface("caption_entities", config.CaptionEntities)
- if config.Performer != "" {
- v.Add("performer", config.Performer)
- }
- if config.Title != "" {
- v.Add("title", config.Title)
- }
- if config.Caption != "" {
- v.Add("caption", config.Caption)
- if config.ParseMode != "" {
- v.Add("parse_mode", config.ParseMode)
- }
- }
+ return params, err
+}
- return v, nil
+func (config PhotoConfig) method() string {
+ return "sendPhoto"
}
-// params returns a map[string]string representation of AudioConfig.
-func (config AudioConfig) params() (map[string]string, error) {
- params, _ := config.BaseFile.params()
+func (config PhotoConfig) files() []RequestFile {
+ files := []RequestFile{{
+ Name: "photo",
+ Data: config.File,
+ }}
- if config.Duration != 0 {
- params["duration"] = strconv.Itoa(config.Duration)
+ if config.Thumb != nil {
+ files = append(files, RequestFile{
+ Name: "thumb",
+ Data: config.Thumb,
+ })
}
- if config.Performer != "" {
- params["performer"] = config.Performer
- }
- if config.Title != "" {
- params["title"] = config.Title
- }
- if config.Caption != "" {
- params["caption"] = config.Caption
- if config.ParseMode != "" {
- params["parse_mode"] = config.ParseMode
- }
- }
+ return files
+}
- return params, nil
+// AudioConfig contains information about a SendAudio request.
+type AudioConfig struct {
+ BaseFile
+ Thumb RequestFileData
+ Caption string
+ ParseMode string
+ CaptionEntities []MessageEntity
+ Duration int
+ Performer string
+ Title string
}
-// name returns the field name for the Audio.
-func (config AudioConfig) name() string {
- return "audio"
+func (config AudioConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
+ if err != nil {
+ return params, err
+ }
+
+ params.AddNonZero("duration", config.Duration)
+ params.AddNonEmpty("performer", config.Performer)
+ params.AddNonEmpty("title", config.Title)
+ params.AddNonEmpty("caption", config.Caption)
+ params.AddNonEmpty("parse_mode", config.ParseMode)
+ err = params.AddInterface("caption_entities", config.CaptionEntities)
+
+ return params, err
}
-// method returns Telegram API method name for sending Audio.
func (config AudioConfig) method() string {
return "sendAudio"
}
-// DocumentConfig contains information about a SendDocument request.
-type DocumentConfig struct {
- BaseFile
- Caption string
- ParseMode string
-}
+func (config AudioConfig) files() []RequestFile {
+ files := []RequestFile{{
+ Name: "audio",
+ Data: config.File,
+ }}
-// values returns a url.Values representation of DocumentConfig.
-func (config DocumentConfig) values() (url.Values, error) {
- v, err := config.BaseChat.values()
- if err != nil {
- return v, err
- }
-
- v.Add(config.name(), config.FileID)
- if config.Caption != "" {
- v.Add("caption", config.Caption)
- if config.ParseMode != "" {
- v.Add("parse_mode", config.ParseMode)
- }
+ if config.Thumb != nil {
+ files = append(files, RequestFile{
+ Name: "thumb",
+ Data: config.Thumb,
+ })
}
- return v, nil
+ return files
}
-// params returns a map[string]string representation of DocumentConfig.
-func (config DocumentConfig) params() (map[string]string, error) {
- params, _ := config.BaseFile.params()
+// DocumentConfig contains information about a SendDocument request.
+type DocumentConfig struct {
+ BaseFile
+ Thumb RequestFileData
+ Caption string
+ ParseMode string
+ CaptionEntities []MessageEntity
+ DisableContentTypeDetection bool
+}
- if config.Caption != "" {
- params["caption"] = config.Caption
- if config.ParseMode != "" {
- params["parse_mode"] = config.ParseMode
- }
- }
+func (config DocumentConfig) params() (Params, error) {
+ params, err := config.BaseFile.params()
- return params, nil
-}
+ params.AddNonEmpty("caption", config.Caption)
+ params.AddNonEmpty("parse_mode", config.ParseMode)
+ params.AddBool("disable_content_type_detection", config.DisableContentTypeDetection)
-// name returns the field name for the Document.
-func (config DocumentConfig) name() string {
- return "document"
+ return params, err
}
-// method returns Telegram API method name for sending Document.
func (config DocumentConfig) method() string {
return "sendDocument"
}
-// StickerConfig contains information about a SendSticker request.
-type StickerConfig struct {
- BaseFile
-}
+func (config DocumentConfig) files() []RequestFile {
+ files := []RequestFile{{
+ Name: "document",
+ Data: config.File,
+ }}
-// values returns a url.Values representation of StickerConfig.
-func (config StickerConfig) values() (url.Values, error) {
- v, err := config.BaseChat.values()
- if err != nil {
- return v, err
+ if config.Thumb != nil {
+ files = append(files, RequestFile{
+ Name: "thumb",
+ Data: config.Thumb,
+ })
}
- v.Add(config.name(), config.FileID)
-
- return v, nil
+ return files
}
-// params returns a map[string]string representation of StickerConfig.
-func (config StickerConfig) params() (map[string]string, error) {
- params, _ := config.BaseFile.params()
-
- return params, nil
+// StickerConfig contains information about a SendSticker request.
+type StickerConfig struct {
+ BaseFile
}
-// name returns the field name for the Sticker.
-func (config StickerConfig) name() string {
- return "sticker"
+func (config StickerConfig) params() (Params, error) {
+ return config.BaseChat.params()
}
-// method returns Telegram API method name for sending Sticker.
func (config StickerConfig) method() string {
return "sendSticker"
}
+func (config StickerConfig) files() []RequestFile {
+ return []RequestFile{{
+ Name: "sticker",
+ Data: config.File,
+ }}
+}
+
// VideoConfig contains information about a SendVideo request.
type VideoConfig struct {
BaseFile
- Duration int
- Caption string
- ParseMode string
+ Thumb RequestFileData
+ Duration int
+ Caption string
+ ParseMode string
+ CaptionEntities []MessageEntity
+ SupportsStreaming bool
}
-// values returns a url.Values representation of VideoConfig.
-func (config VideoConfig) values() (url.Values, error) {
- v, err := config.BaseChat.values()
+func (config VideoConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
if err != nil {
- return v, err
+ return params, err
}
- v.Add(config.name(), config.FileID)
- if config.Duration != 0 {
- v.Add("duration", strconv.Itoa(config.Duration))
- }
- if config.Caption != "" {
- v.Add("caption", config.Caption)
- if config.ParseMode != "" {
- v.Add("parse_mode", config.ParseMode)
- }
- }
+ params.AddNonZero("duration", config.Duration)
+ params.AddNonEmpty("caption", config.Caption)
+ params.AddNonEmpty("parse_mode", config.ParseMode)
+ params.AddBool("supports_streaming", config.SupportsStreaming)
+ err = params.AddInterface("caption_entities", config.CaptionEntities)
- return v, nil
+ return params, err
}
-// params returns a map[string]string representation of VideoConfig.
-func (config VideoConfig) params() (map[string]string, error) {
- params, _ := config.BaseFile.params()
-
- if config.Caption != "" {
- params["caption"] = config.Caption
- if config.ParseMode != "" {
- params["parse_mode"] = config.ParseMode
- }
- }
-
- return params, nil
+func (config VideoConfig) method() string {
+ return "sendVideo"
}
-// name returns the field name for the Video.
-func (config VideoConfig) name() string {
- return "video"
-}
+func (config VideoConfig) files() []RequestFile {
+ files := []RequestFile{{
+ Name: "video",
+ Data: config.File,
+ }}
-// method returns Telegram API method name for sending Video.
-func (config VideoConfig) method() string {
- return "sendVideo"
+ if config.Thumb != nil {
+ files = append(files, RequestFile{
+ Name: "thumb",
+ Data: config.Thumb,
+ })
+ }
+
+ return files
}
// AnimationConfig contains information about a SendAnimation request.
type AnimationConfig struct {
BaseFile
- Duration int
- Caption string
- ParseMode string
+ Duration int
+ Thumb RequestFileData
+ Caption string
+ ParseMode string
+ CaptionEntities []MessageEntity
}
-// values returns a url.Values representation of AnimationConfig.
-func (config AnimationConfig) values() (url.Values, error) {
- v, err := config.BaseChat.values()
+func (config AnimationConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
if err != nil {
- return v, err
+ return params, err
}
- v.Add(config.name(), config.FileID)
- if config.Duration != 0 {
- v.Add("duration", strconv.Itoa(config.Duration))
- }
- if config.Caption != "" {
- v.Add("caption", config.Caption)
- if config.ParseMode != "" {
- v.Add("parse_mode", config.ParseMode)
- }
- }
+ params.AddNonZero("duration", config.Duration)
+ params.AddNonEmpty("caption", config.Caption)
+ params.AddNonEmpty("parse_mode", config.ParseMode)
+ err = params.AddInterface("caption_entities", config.CaptionEntities)
- return v, nil
+ return params, err
}
-// params returns a map[string]string representation of AnimationConfig.
-func (config AnimationConfig) params() (map[string]string, error) {
- params, _ := config.BaseFile.params()
-
- if config.Caption != "" {
- params["caption"] = config.Caption
- if config.ParseMode != "" {
- params["parse_mode"] = config.ParseMode
- }
- }
-
- return params, nil
+func (config AnimationConfig) method() string {
+ return "sendAnimation"
}
-// name returns the field name for the Animation.
-func (config AnimationConfig) name() string {
- return "animation"
-}
+func (config AnimationConfig) files() []RequestFile {
+ files := []RequestFile{{
+ Name: "animation",
+ Data: config.File,
+ }}
-// method returns Telegram API method name for sending Animation.
-func (config AnimationConfig) method() string {
- return "sendAnimation"
+ if config.Thumb != nil {
+ files = append(files, RequestFile{
+ Name: "thumb",
+ Data: config.Thumb,
+ })
+ }
+
+ return files
}
// VideoNoteConfig contains information about a SendVideoNote request.
type VideoNoteConfig struct {
BaseFile
+ Thumb RequestFileData
Duration int
Length int
}
-// values returns a url.Values representation of VideoNoteConfig.
-func (config VideoNoteConfig) values() (url.Values, error) {
- v, err := config.BaseChat.values()
- if err != nil {
- return v, err
- }
+func (config VideoNoteConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
- v.Add(config.name(), config.FileID)
- if config.Duration != 0 {
- v.Add("duration", strconv.Itoa(config.Duration))
- }
+ params.AddNonZero("duration", config.Duration)
+ params.AddNonZero("length", config.Length)
- // Telegram API seems to have a bug, if no length is provided or it is 0, it will send an error response
- if config.Length != 0 {
- v.Add("length", strconv.Itoa(config.Length))
- }
+ return params, err
+}
- return v, nil
+func (config VideoNoteConfig) method() string {
+ return "sendVideoNote"
}
-// params returns a map[string]string representation of VideoNoteConfig.
-func (config VideoNoteConfig) params() (map[string]string, error) {
- params, _ := config.BaseFile.params()
+func (config VideoNoteConfig) files() []RequestFile {
+ files := []RequestFile{{
+ Name: "video_note",
+ Data: config.File,
+ }}
- if config.Length != 0 {
- params["length"] = strconv.Itoa(config.Length)
- }
- if config.Duration != 0 {
- params["duration"] = strconv.Itoa(config.Duration)
+ if config.Thumb != nil {
+ files = append(files, RequestFile{
+ Name: "thumb",
+ Data: config.Thumb,
+ })
}
- return params, nil
-}
-
-// name returns the field name for the VideoNote.
-func (config VideoNoteConfig) name() string {
- return "video_note"
-}
-
-// method returns Telegram API method name for sending VideoNote.
-func (config VideoNoteConfig) method() string {
- return "sendVideoNote"
+ return files
}
// VoiceConfig contains information about a SendVoice request.
type VoiceConfig struct {
BaseFile
- Caption string
- ParseMode string
- Duration int
+ Thumb RequestFileData
+ Caption string
+ ParseMode string
+ CaptionEntities []MessageEntity
+ Duration int
}
-// values returns a url.Values representation of VoiceConfig.
-func (config VoiceConfig) values() (url.Values, error) {
- v, err := config.BaseChat.values()
+func (config VoiceConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
if err != nil {
- return v, err
+ return params, err
}
- v.Add(config.name(), config.FileID)
- if config.Duration != 0 {
- v.Add("duration", strconv.Itoa(config.Duration))
- }
- if config.Caption != "" {
- v.Add("caption", config.Caption)
- if config.ParseMode != "" {
- v.Add("parse_mode", config.ParseMode)
- }
- }
+ params.AddNonZero("duration", config.Duration)
+ params.AddNonEmpty("caption", config.Caption)
+ params.AddNonEmpty("parse_mode", config.ParseMode)
+ err = params.AddInterface("caption_entities", config.CaptionEntities)
- return v, nil
+ return params, err
}
-// params returns a map[string]string representation of VoiceConfig.
-func (config VoiceConfig) params() (map[string]string, error) {
- params, _ := config.BaseFile.params()
-
- if config.Duration != 0 {
- params["duration"] = strconv.Itoa(config.Duration)
- }
- if config.Caption != "" {
- params["caption"] = config.Caption
- if config.ParseMode != "" {
- params["parse_mode"] = config.ParseMode
- }
- }
-
- return params, nil
+func (config VoiceConfig) method() string {
+ return "sendVoice"
}
-// name returns the field name for the Voice.
-func (config VoiceConfig) name() string {
- return "voice"
-}
+func (config VoiceConfig) files() []RequestFile {
+ files := []RequestFile{{
+ Name: "voice",
+ Data: config.File,
+ }}
-// method returns Telegram API method name for sending Voice.
-func (config VoiceConfig) method() string {
- return "sendVoice"
+ if config.Thumb != nil {
+ files = append(files, RequestFile{
+ Name: "thumb",
+ Data: config.Thumb,
+ })
+ }
+
+ return files
}
-// MediaGroupConfig contains information about a sendMediaGroup request.
-type MediaGroupConfig struct {
+// LocationConfig contains information about a SendLocation request.
+type LocationConfig struct {
BaseChat
- InputMedia []interface{}
+ Latitude float64 // required
+ Longitude float64 // required
+ HorizontalAccuracy float64 // optional
+ LivePeriod int // optional
+ Heading int // optional
+ ProximityAlertRadius int // optional
}
-func (config MediaGroupConfig) values() (url.Values, error) {
- v, err := config.BaseChat.values()
- if err != nil {
- return v, err
- }
+func (config LocationConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
- data, err := json.Marshal(config.InputMedia)
- if err != nil {
- return v, err
- }
+ params.AddNonZeroFloat("latitude", config.Latitude)
+ params.AddNonZeroFloat("longitude", config.Longitude)
+ params.AddNonZeroFloat("horizontal_accuracy", config.HorizontalAccuracy)
+ params.AddNonZero("live_period", config.LivePeriod)
+ params.AddNonZero("heading", config.Heading)
+ params.AddNonZero("proximity_alert_radius", config.ProximityAlertRadius)
- v.Add("media", string(data))
+ return params, err
+}
- return v, nil
+func (config LocationConfig) method() string {
+ return "sendLocation"
}
-func (config MediaGroupConfig) method() string {
- return "sendMediaGroup"
+// EditMessageLiveLocationConfig allows you to update a live location.
+type EditMessageLiveLocationConfig struct {
+ BaseEdit
+ Latitude float64 // required
+ Longitude float64 // required
+ HorizontalAccuracy float64 // optional
+ Heading int // optional
+ ProximityAlertRadius int // optional
}
-// LocationConfig contains information about a SendLocation request.
-type LocationConfig struct {
- BaseChat
- Latitude float64 // required
- Longitude float64 // required
+func (config EditMessageLiveLocationConfig) params() (Params, error) {
+ params, err := config.BaseEdit.params()
+
+ params.AddNonZeroFloat("latitude", config.Latitude)
+ params.AddNonZeroFloat("longitude", config.Longitude)
+ params.AddNonZeroFloat("horizontal_accuracy", config.HorizontalAccuracy)
+ params.AddNonZero("heading", config.Heading)
+ params.AddNonZero("proximity_alert_radius", config.ProximityAlertRadius)
+
+ return params, err
}
-// values returns a url.Values representation of LocationConfig.
-func (config LocationConfig) values() (url.Values, error) {
- v, err := config.BaseChat.values()
- if err != nil {
- return v, err
- }
+func (config EditMessageLiveLocationConfig) method() string {
+ return "editMessageLiveLocation"
+}
- v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64))
- v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64))
+// StopMessageLiveLocationConfig stops updating a live location.
+type StopMessageLiveLocationConfig struct {
+ BaseEdit
+}
- return v, nil
+func (config StopMessageLiveLocationConfig) params() (Params, error) {
+ return config.BaseEdit.params()
}
-// method returns Telegram API method name for sending Location.
-func (config LocationConfig) method() string {
- return "sendLocation"
+func (config StopMessageLiveLocationConfig) method() string {
+ return "stopMessageLiveLocation"
}
// VenueConfig contains information about a SendVenue request.
type VenueConfig struct {
BaseChat
- Latitude float64 // required
- Longitude float64 // required
- Title string // required
- Address string // required
- FoursquareID string
+ Latitude float64 // required
+ Longitude float64 // required
+ Title string // required
+ Address string // required
+ FoursquareID string
+ FoursquareType string
+ GooglePlaceID string
+ GooglePlaceType string
}
-func (config VenueConfig) values() (url.Values, error) {
- v, err := config.BaseChat.values()
- if err != nil {
- return v, err
- }
+func (config VenueConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
- v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64))
- v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64))
- v.Add("title", config.Title)
- v.Add("address", config.Address)
- if config.FoursquareID != "" {
- v.Add("foursquare_id", config.FoursquareID)
- }
+ params.AddNonZeroFloat("latitude", config.Latitude)
+ params.AddNonZeroFloat("longitude", config.Longitude)
+ params["title"] = config.Title
+ params["address"] = config.Address
+ params.AddNonEmpty("foursquare_id", config.FoursquareID)
+ params.AddNonEmpty("foursquare_type", config.FoursquareType)
+ params.AddNonEmpty("google_place_id", config.GooglePlaceID)
+ params.AddNonEmpty("google_place_type", config.GooglePlaceType)
- return v, nil
+ return params, err
}
func (config VenueConfig) method() string {
@@ -758,19 +830,19 @@ type ContactConfig struct {
PhoneNumber string
FirstName string
LastName string
+ VCard string
}
-func (config ContactConfig) values() (url.Values, error) {
- v, err := config.BaseChat.values()
- if err != nil {
- return v, err
- }
+func (config ContactConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
+
+ params["phone_number"] = config.PhoneNumber
+ params["first_name"] = config.FirstName
- v.Add("phone_number", config.PhoneNumber)
- v.Add("first_name", config.FirstName)
- v.Add("last_name", config.LastName)
+ params.AddNonEmpty("last_name", config.LastName)
+ params.AddNonEmpty("vcard", config.VCard)
- return v, nil
+ return params, err
}
func (config ContactConfig) method() string {
@@ -788,19 +860,22 @@ type SendPollConfig struct {
CorrectOptionID int64
Explanation string
ExplanationParseMode string
+ ExplanationEntities []MessageEntity
OpenPeriod int
CloseDate int
IsClosed bool
}
-func (config SendPollConfig) values() (url.Values, error) {
+func (config SendPollConfig) params() (Params, error) {
params, err := config.BaseChat.params()
if err != nil {
- return params.toValues(), err
+ return params, err
}
params["question"] = config.Question
- err = params.AddInterface("options", config.Options)
+ if err = params.AddInterface("options", config.Options); err != nil {
+ return params, err
+ }
params["is_anonymous"] = strconv.FormatBool(config.IsAnonymous)
params.AddNonEmpty("type", config.Type)
params["allows_multiple_answers"] = strconv.FormatBool(config.AllowsMultipleAnswers)
@@ -810,8 +885,9 @@ func (config SendPollConfig) values() (url.Values, error) {
params.AddNonEmpty("explanation_parse_mode", config.ExplanationParseMode)
params.AddNonZero("open_period", config.OpenPeriod)
params.AddNonZero("close_date", config.CloseDate)
+ err = params.AddInterface("explanation_entities", config.ExplanationEntities)
- return params.toValues(), err
+ return params, err
}
func (SendPollConfig) method() string {
@@ -824,15 +900,12 @@ type GameConfig struct {
GameShortName string
}
-func (config GameConfig) values() (url.Values, error) {
- v, err := config.BaseChat.values()
- if err != nil {
- return v, err
- }
+func (config GameConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
- v.Add("game_short_name", config.GameShortName)
+ params["game_short_name"] = config.GameShortName
- return v, nil
+ return params, err
}
func (config GameConfig) method() string {
@@ -841,7 +914,7 @@ func (config GameConfig) method() string {
// SetGameScoreConfig allows you to update the game score in a chat.
type SetGameScoreConfig struct {
- UserID int
+ UserID int64
Score int
Force bool
DisableEditMessage bool
@@ -851,24 +924,21 @@ type SetGameScoreConfig struct {
InlineMessageID string
}
-func (config SetGameScoreConfig) values() (url.Values, error) {
- v := url.Values{}
+func (config SetGameScoreConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddNonZero64("user_id", config.UserID)
+ params.AddNonZero("scrore", config.Score)
+ params.AddBool("disable_edit_message", config.DisableEditMessage)
- v.Add("user_id", strconv.Itoa(config.UserID))
- v.Add("score", strconv.Itoa(config.Score))
- if config.InlineMessageID == "" {
- if config.ChannelUsername == "" {
- v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
- } else {
- v.Add("chat_id", config.ChannelUsername)
- }
- v.Add("message_id", strconv.Itoa(config.MessageID))
+ if config.InlineMessageID != "" {
+ params["inline_message_id"] = config.InlineMessageID
} else {
- v.Add("inline_message_id", config.InlineMessageID)
+ params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
+ params.AddNonZero("message_id", config.MessageID)
}
- v.Add("disable_edit_message", strconv.FormatBool(config.DisableEditMessage))
- return v, nil
+ return params, nil
}
func (config SetGameScoreConfig) method() string {
@@ -877,29 +947,26 @@ func (config SetGameScoreConfig) method() string {
// GetGameHighScoresConfig allows you to fetch the high scores for a game.
type GetGameHighScoresConfig struct {
- UserID int
- ChatID int
+ UserID int64
+ ChatID int64
ChannelUsername string
MessageID int
InlineMessageID string
}
-func (config GetGameHighScoresConfig) values() (url.Values, error) {
- v := url.Values{}
+func (config GetGameHighScoresConfig) params() (Params, error) {
+ params := make(Params)
- v.Add("user_id", strconv.Itoa(config.UserID))
- if config.InlineMessageID == "" {
- if config.ChannelUsername == "" {
- v.Add("chat_id", strconv.Itoa(config.ChatID))
- } else {
- v.Add("chat_id", config.ChannelUsername)
- }
- v.Add("message_id", strconv.Itoa(config.MessageID))
+ params.AddNonZero64("user_id", config.UserID)
+
+ if config.InlineMessageID != "" {
+ params["inline_message_id"] = config.InlineMessageID
} else {
- v.Add("inline_message_id", config.InlineMessageID)
+ params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
+ params.AddNonZero("message_id", config.MessageID)
}
- return v, nil
+ return params, nil
}
func (config GetGameHighScoresConfig) method() string {
@@ -912,17 +979,14 @@ type ChatActionConfig struct {
Action string // required
}
-// values returns a url.Values representation of ChatActionConfig.
-func (config ChatActionConfig) values() (url.Values, error) {
- v, err := config.BaseChat.values()
- if err != nil {
- return v, err
- }
- v.Add("action", config.Action)
- return v, nil
+func (config ChatActionConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
+
+ params["action"] = config.Action
+
+ return params, err
}
-// method returns Telegram API method name for sending ChatAction.
func (config ChatActionConfig) method() string {
return "sendChatAction"
}
@@ -932,20 +996,22 @@ type EditMessageTextConfig struct {
BaseEdit
Text string
ParseMode string
+ Entities []MessageEntity
DisableWebPagePreview bool
}
-func (config EditMessageTextConfig) values() (url.Values, error) {
- v, err := config.BaseEdit.values()
+func (config EditMessageTextConfig) params() (Params, error) {
+ params, err := config.BaseEdit.params()
if err != nil {
- return v, err
+ return params, err
}
- v.Add("text", config.Text)
- v.Add("parse_mode", config.ParseMode)
- v.Add("disable_web_page_preview", strconv.FormatBool(config.DisableWebPagePreview))
+ params["text"] = config.Text
+ params.AddNonEmpty("parse_mode", config.ParseMode)
+ params.AddBool("disable_web_page_preview", config.DisableWebPagePreview)
+ err = params.AddInterface("entities", config.Entities)
- return v, nil
+ return params, err
}
func (config EditMessageTextConfig) method() string {
@@ -955,80 +1021,198 @@ func (config EditMessageTextConfig) method() string {
// EditMessageCaptionConfig allows you to modify the caption of a message.
type EditMessageCaptionConfig struct {
BaseEdit
- Caption string
- ParseMode string
+ Caption string
+ ParseMode string
+ CaptionEntities []MessageEntity
}
-func (config EditMessageCaptionConfig) values() (url.Values, error) {
- v, _ := config.BaseEdit.values()
-
- v.Add("caption", config.Caption)
- if config.ParseMode != "" {
- v.Add("parse_mode", config.ParseMode)
+func (config EditMessageCaptionConfig) params() (Params, error) {
+ params, err := config.BaseEdit.params()
+ if err != nil {
+ return params, err
}
- return v, nil
+ params["caption"] = config.Caption
+ params.AddNonEmpty("parse_mode", config.ParseMode)
+ err = params.AddInterface("caption_entities", config.CaptionEntities)
+
+ return params, err
}
func (config EditMessageCaptionConfig) method() string {
return "editMessageCaption"
}
+// EditMessageMediaConfig allows you to make an editMessageMedia request.
+type EditMessageMediaConfig struct {
+ BaseEdit
+
+ Media interface{}
+}
+
+func (EditMessageMediaConfig) method() string {
+ return "editMessageMedia"
+}
+
+func (config EditMessageMediaConfig) params() (Params, error) {
+ params, err := config.BaseEdit.params()
+ if err != nil {
+ return params, err
+ }
+
+ err = params.AddInterface("media", prepareInputMediaParam(config.Media, 0))
+
+ return params, err
+}
+
+func (config EditMessageMediaConfig) files() []RequestFile {
+ return prepareInputMediaFile(config.Media, 0)
+}
+
// EditMessageReplyMarkupConfig allows you to modify the reply markup
// of a message.
type EditMessageReplyMarkupConfig struct {
BaseEdit
}
-func (config EditMessageReplyMarkupConfig) values() (url.Values, error) {
- return config.BaseEdit.values()
+func (config EditMessageReplyMarkupConfig) params() (Params, error) {
+ return config.BaseEdit.params()
}
func (config EditMessageReplyMarkupConfig) method() string {
return "editMessageReplyMarkup"
}
+// StopPollConfig allows you to stop a poll sent by the bot.
+type StopPollConfig struct {
+ BaseEdit
+}
+
+func (config StopPollConfig) params() (Params, error) {
+ return config.BaseEdit.params()
+}
+
+func (StopPollConfig) method() string {
+ return "stopPoll"
+}
+
// UserProfilePhotosConfig contains information about a
// GetUserProfilePhotos request.
type UserProfilePhotosConfig struct {
- UserID int
+ UserID int64
Offset int
Limit int
}
+func (UserProfilePhotosConfig) method() string {
+ return "getUserProfilePhotos"
+}
+
+func (config UserProfilePhotosConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddNonZero64("user_id", config.UserID)
+ params.AddNonZero("offset", config.Offset)
+ params.AddNonZero("limit", config.Limit)
+
+ return params, nil
+}
+
// FileConfig has information about a file hosted on Telegram.
type FileConfig struct {
FileID string
}
+func (FileConfig) method() string {
+ return "getFile"
+}
+
+func (config FileConfig) params() (Params, error) {
+ params := make(Params)
+
+ params["file_id"] = config.FileID
+
+ return params, nil
+}
+
// UpdateConfig contains information about a GetUpdates request.
type UpdateConfig struct {
- Offset int
- Limit int
- Timeout int
+ Offset int
+ Limit int
+ Timeout int
+ AllowedUpdates []string
+}
+
+func (UpdateConfig) method() string {
+ return "getUpdates"
+}
+
+func (config UpdateConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddNonZero("offset", config.Offset)
+ params.AddNonZero("limit", config.Limit)
+ params.AddNonZero("timeout", config.Timeout)
+ params.AddInterface("allowed_updates", config.AllowedUpdates)
+
+ return params, nil
}
// WebhookConfig contains information about a SetWebhook request.
type WebhookConfig struct {
- URL *url.URL
- Certificate interface{}
- MaxConnections int
+ URL *url.URL
+ Certificate RequestFileData
+ IPAddress string
+ MaxConnections int
+ AllowedUpdates []string
+ DropPendingUpdates bool
}
-// FileBytes contains information about a set of bytes to upload
-// as a File.
-type FileBytes struct {
- Name string
- Bytes []byte
+func (config WebhookConfig) method() string {
+ return "setWebhook"
}
-// FileReader contains information about a reader to upload as a File.
-// If Size is -1, it will read the entire Reader into memory to
-// calculate a Size.
-type FileReader struct {
- Name string
- Reader io.Reader
- Size int64
+func (config WebhookConfig) params() (Params, error) {
+ params := make(Params)
+
+ if config.URL != nil {
+ params["url"] = config.URL.String()
+ }
+
+ params.AddNonEmpty("ip_address", config.IPAddress)
+ params.AddNonZero("max_connections", config.MaxConnections)
+ err := params.AddInterface("allowed_updates", config.AllowedUpdates)
+ params.AddBool("drop_pending_updates", config.DropPendingUpdates)
+
+ return params, err
+}
+
+func (config WebhookConfig) files() []RequestFile {
+ if config.Certificate != nil {
+ return []RequestFile{{
+ Name: "certificate",
+ Data: config.Certificate,
+ }}
+ }
+
+ return nil
+}
+
+// DeleteWebhookConfig is a helper to delete a webhook.
+type DeleteWebhookConfig struct {
+ DropPendingUpdates bool
+}
+
+func (config DeleteWebhookConfig) method() string {
+ return "deleteWebhook"
+}
+
+func (config DeleteWebhookConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddBool("drop_pending_updates", config.DropPendingUpdates)
+
+ return params, nil
}
// InlineConfig contains information on making an InlineQuery response.
@@ -1042,6 +1226,24 @@ type InlineConfig struct {
SwitchPMParameter string `json:"switch_pm_parameter"`
}
+func (config InlineConfig) method() string {
+ return "answerInlineQuery"
+}
+
+func (config InlineConfig) params() (Params, error) {
+ params := make(Params)
+
+ params["inline_query_id"] = config.InlineQueryID
+ params.AddNonZero("cache_time", config.CacheTime)
+ params.AddBool("is_personal", config.IsPersonal)
+ params.AddNonEmpty("next_offset", config.NextOffset)
+ params.AddNonEmpty("switch_pm_text", config.SwitchPMText)
+ params.AddNonEmpty("switch_pm_parameter", config.SwitchPMParameter)
+ err := params.AddInterface("results", config.Results)
+
+ return params, err
+}
+
// CallbackConfig contains information on making a CallbackQuery response.
type CallbackConfig struct {
CallbackQueryID string `json:"callback_query_id"`
@@ -1051,279 +1253,667 @@ type CallbackConfig struct {
CacheTime int `json:"cache_time"`
}
+func (config CallbackConfig) method() string {
+ return "answerCallbackQuery"
+}
+
+func (config CallbackConfig) params() (Params, error) {
+ params := make(Params)
+
+ params["callback_query_id"] = config.CallbackQueryID
+ params.AddNonEmpty("text", config.Text)
+ params.AddBool("show_alert", config.ShowAlert)
+ params.AddNonEmpty("url", config.URL)
+ params.AddNonZero("cache_time", config.CacheTime)
+
+ return params, nil
+}
+
// ChatMemberConfig contains information about a user in a chat for use
// with administrative functions such as kicking or unbanning a user.
type ChatMemberConfig struct {
ChatID int64
SuperGroupUsername string
ChannelUsername string
- UserID int
+ UserID int64
}
-// KickChatMemberConfig contains extra fields to kick user
-type KickChatMemberConfig struct {
+// UnbanChatMemberConfig allows you to unban a user.
+type UnbanChatMemberConfig struct {
ChatMemberConfig
- UntilDate int64
+ OnlyIfBanned bool
}
-// RestrictChatMemberConfig contains fields to restrict members of chat
-type RestrictChatMemberConfig struct {
- ChatMemberConfig
- UntilDate int64
- CanSendMessages *bool
- CanSendMediaMessages *bool
- CanSendOtherMessages *bool
- CanAddWebPagePreviews *bool
+func (config UnbanChatMemberConfig) method() string {
+ return "unbanChatMember"
}
-// PromoteChatMemberConfig contains fields to promote members of chat
-type PromoteChatMemberConfig struct {
- ChatMemberConfig
- CanChangeInfo *bool
- CanPostMessages *bool
- CanEditMessages *bool
- CanDeleteMessages *bool
- CanInviteUsers *bool
- CanRestrictMembers *bool
- CanPinMessages *bool
- CanPromoteMembers *bool
-}
+func (config UnbanChatMemberConfig) params() (Params, error) {
+ params := make(Params)
-// ChatConfig contains information about getting information on a chat.
-type ChatConfig struct {
- ChatID int64
- SuperGroupUsername string
-}
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername, config.ChannelUsername)
+ params.AddNonZero64("user_id", config.UserID)
+ params.AddBool("only_if_banned", config.OnlyIfBanned)
-// ChatConfigWithUser contains information about getting information on
-// a specific user within a chat.
-type ChatConfigWithUser struct {
- ChatID int64
- SuperGroupUsername string
- UserID int
+ return params, nil
}
-// InvoiceConfig contains information for sendInvoice request.
-type InvoiceConfig struct {
- BaseChat
- Title string // required
- Description string // required
- Payload string // required
- ProviderToken string // required
- StartParameter string // required
- Currency string // required
- Prices *[]LabeledPrice // required
- PhotoURL string
- PhotoSize int
- PhotoWidth int
- PhotoHeight int
- NeedName bool
- NeedPhoneNumber bool
- NeedEmail bool
- NeedShippingAddress bool
- IsFlexible bool
-}
-
-func (config InvoiceConfig) values() (url.Values, error) {
- v, err := config.BaseChat.values()
- if err != nil {
- return v, err
- }
- v.Add("title", config.Title)
- v.Add("description", config.Description)
- v.Add("payload", config.Payload)
- v.Add("provider_token", config.ProviderToken)
- v.Add("start_parameter", config.StartParameter)
- v.Add("currency", config.Currency)
- data, err := json.Marshal(config.Prices)
- if err != nil {
- return v, err
- }
- v.Add("prices", string(data))
- if config.PhotoURL != "" {
- v.Add("photo_url", config.PhotoURL)
- }
- if config.PhotoSize != 0 {
- v.Add("photo_size", strconv.Itoa(config.PhotoSize))
- }
- if config.PhotoWidth != 0 {
- v.Add("photo_width", strconv.Itoa(config.PhotoWidth))
- }
- if config.PhotoHeight != 0 {
- v.Add("photo_height", strconv.Itoa(config.PhotoHeight))
- }
- if config.NeedName != false {
- v.Add("need_name", strconv.FormatBool(config.NeedName))
- }
- if config.NeedPhoneNumber != false {
- v.Add("need_phone_number", strconv.FormatBool(config.NeedPhoneNumber))
- }
- if config.NeedEmail != false {
- v.Add("need_email", strconv.FormatBool(config.NeedEmail))
- }
- if config.NeedShippingAddress != false {
- v.Add("need_shipping_address", strconv.FormatBool(config.NeedShippingAddress))
- }
- if config.IsFlexible != false {
- v.Add("is_flexible", strconv.FormatBool(config.IsFlexible))
- }
-
- return v, nil
+// KickChatMemberConfig contains extra fields to kick user
+type KickChatMemberConfig struct {
+ ChatMemberConfig
+ UntilDate int64
+ RevokeMessages bool
}
-func (config InvoiceConfig) method() string {
- return "sendInvoice"
+func (config KickChatMemberConfig) method() string {
+ return "kickChatMember"
}
-// ShippingConfig contains information for answerShippingQuery request.
-type ShippingConfig struct {
- ShippingQueryID string // required
- OK bool // required
- ShippingOptions *[]ShippingOption
- ErrorMessage string
-}
+func (config KickChatMemberConfig) params() (Params, error) {
+ params := make(Params)
-// PreCheckoutConfig conatins information for answerPreCheckoutQuery request.
-type PreCheckoutConfig struct {
- PreCheckoutQueryID string // required
- OK bool // required
- ErrorMessage string
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername)
+ params.AddNonZero64("user_id", config.UserID)
+ params.AddNonZero64("until_date", config.UntilDate)
+ params.AddBool("revoke_messages", config.RevokeMessages)
+
+ return params, nil
}
-// DeleteMessageConfig contains information of a message in a chat to delete.
-type DeleteMessageConfig struct {
- ChannelUsername string
- ChatID int64
- MessageID int
+// RestrictChatMemberConfig contains fields to restrict members of chat
+type RestrictChatMemberConfig struct {
+ ChatMemberConfig
+ UntilDate int64
+ Permissions *ChatPermissions
}
-func (config DeleteMessageConfig) method() string {
- return "deleteMessage"
+func (config RestrictChatMemberConfig) method() string {
+ return "restrictChatMember"
}
-func (config DeleteMessageConfig) values() (url.Values, error) {
- v := url.Values{}
+func (config RestrictChatMemberConfig) params() (Params, error) {
+ params := make(Params)
- if config.ChannelUsername == "" {
- v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
- } else {
- v.Add("chat_id", config.ChannelUsername)
- }
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername, config.ChannelUsername)
+ params.AddNonZero64("user_id", config.UserID)
- v.Add("message_id", strconv.Itoa(config.MessageID))
+ err := params.AddInterface("permissions", config.Permissions)
+ params.AddNonZero64("until_date", config.UntilDate)
- return v, nil
+ return params, err
}
-// PinChatMessageConfig contains information of a message in a chat to pin.
-type PinChatMessageConfig struct {
- ChatID int64
- MessageID int
- DisableNotification bool
+// PromoteChatMemberConfig contains fields to promote members of chat
+type PromoteChatMemberConfig struct {
+ ChatMemberConfig
+ IsAnonymous bool
+ CanManageChat bool
+ CanChangeInfo bool
+ CanPostMessages bool
+ CanEditMessages bool
+ CanDeleteMessages bool
+ CanManageVoiceChats bool
+ CanInviteUsers bool
+ CanRestrictMembers bool
+ CanPinMessages bool
+ CanPromoteMembers bool
}
-func (config PinChatMessageConfig) method() string {
- return "pinChatMessage"
+func (config PromoteChatMemberConfig) method() string {
+ return "promoteChatMember"
}
-func (config PinChatMessageConfig) values() (url.Values, error) {
- v := url.Values{}
+func (config PromoteChatMemberConfig) params() (Params, error) {
+ params := make(Params)
- v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
- v.Add("message_id", strconv.Itoa(config.MessageID))
- v.Add("disable_notification", strconv.FormatBool(config.DisableNotification))
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername, config.ChannelUsername)
+ params.AddNonZero64("user_id", config.UserID)
+
+ params.AddBool("is_anonymous", config.IsAnonymous)
+ params.AddBool("can_manage_chat", config.CanManageChat)
+ params.AddBool("can_change_info", config.CanChangeInfo)
+ params.AddBool("can_post_messages", config.CanPostMessages)
+ params.AddBool("can_edit_messages", config.CanEditMessages)
+ params.AddBool("can_delete_messages", config.CanDeleteMessages)
+ params.AddBool("can_manage_voice_chats", config.CanManageVoiceChats)
+ params.AddBool("can_invite_users", config.CanInviteUsers)
+ params.AddBool("can_restrict_members", config.CanRestrictMembers)
+ params.AddBool("can_pin_messages", config.CanPinMessages)
+ params.AddBool("can_promote_members", config.CanPromoteMembers)
- return v, nil
+ return params, nil
}
-// UnpinChatMessageConfig contains information of chat to unpin.
-type UnpinChatMessageConfig struct {
- ChatID int64
+// SetChatAdministratorCustomTitle sets the title of an administrative user
+// promoted by the bot for a chat.
+type SetChatAdministratorCustomTitle struct {
+ ChatMemberConfig
+ CustomTitle string
}
-func (config UnpinChatMessageConfig) method() string {
- return "unpinChatMessage"
+func (SetChatAdministratorCustomTitle) method() string {
+ return "setChatAdministratorCustomTitle"
}
-func (config UnpinChatMessageConfig) values() (url.Values, error) {
- v := url.Values{}
-
- v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
+func (config SetChatAdministratorCustomTitle) params() (Params, error) {
+ params := make(Params)
- return v, nil
-}
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername, config.ChannelUsername)
+ params.AddNonZero64("user_id", config.UserID)
+ params.AddNonEmpty("custom_title", config.CustomTitle)
-// SetChatTitleConfig contains information for change chat title.
-type SetChatTitleConfig struct {
- ChatID int64
- Title string
+ return params, nil
}
-func (config SetChatTitleConfig) method() string {
- return "setChatTitle"
+// ChatConfig contains information about getting information on a chat.
+type ChatConfig struct {
+ ChatID int64
+ SuperGroupUsername string
}
-func (config SetChatTitleConfig) values() (url.Values, error) {
- v := url.Values{}
+func (config ChatConfig) params() (Params, error) {
+ params := make(Params)
- v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
- v.Add("title", config.Title)
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername)
- return v, nil
+ return params, nil
}
-// SetChatDescriptionConfig contains information for change chat description.
-type SetChatDescriptionConfig struct {
- ChatID int64
- Description string
+// ChatInfoConfig contains information about getting chat information.
+type ChatInfoConfig struct {
+ ChatConfig
}
-func (config SetChatDescriptionConfig) method() string {
- return "setChatDescription"
+func (ChatInfoConfig) method() string {
+ return "getChat"
}
-func (config SetChatDescriptionConfig) values() (url.Values, error) {
- v := url.Values{}
+// ChatMemberCountConfig contains information about getting the number of users in a chat.
+type ChatMemberCountConfig struct {
+ ChatConfig
+}
- v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
- v.Add("description", config.Description)
+func (ChatMemberCountConfig) method() string {
+ return "getChatMembersCount"
+}
- return v, nil
+// ChatAdministratorsConfig contains information about getting chat administrators.
+type ChatAdministratorsConfig struct {
+ ChatConfig
}
-// SetChatPhotoConfig contains information for change chat photo
-type SetChatPhotoConfig struct {
- BaseFile
+func (ChatAdministratorsConfig) method() string {
+ return "getChatAdministrators"
+}
+
+// SetChatPermissionsConfig allows you to set default permissions for the
+// members in a group. The bot must be an administrator and have rights to
+// restrict members.
+type SetChatPermissionsConfig struct {
+ ChatConfig
+ Permissions *ChatPermissions
+}
+
+func (SetChatPermissionsConfig) method() string {
+ return "setChatPermissions"
+}
+
+func (config SetChatPermissionsConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername)
+ err := params.AddInterface("permissions", config.Permissions)
+
+ return params, err
+}
+
+// ChatInviteLinkConfig contains information about getting a chat link.
+//
+// Note that generating a new link will revoke any previous links.
+type ChatInviteLinkConfig struct {
+ ChatConfig
+}
+
+func (ChatInviteLinkConfig) method() string {
+ return "exportChatInviteLink"
+}
+
+func (config ChatInviteLinkConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername)
+
+ return params, nil
+}
+
+// CreateChatInviteLinkConfig allows you to create an additional invite link for
+// a chat. The bot must be an administrator in the chat for this to work and
+// must have the appropriate admin rights. The link can be revoked using the
+// RevokeChatInviteLinkConfig.
+type CreateChatInviteLinkConfig struct {
+ ChatConfig
+ Name string
+ ExpireDate int
+ MemberLimit int
+ CreatesJoinRequest bool
+}
+
+func (CreateChatInviteLinkConfig) method() string {
+ return "createChatInviteLink"
+}
+
+func (config CreateChatInviteLinkConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddNonEmpty("name", config.Name)
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername)
+ params.AddNonZero("expire_date", config.ExpireDate)
+ params.AddNonZero("member_limit", config.MemberLimit)
+ params.AddBool("creates_join_request", config.CreatesJoinRequest)
+
+ return params, nil
+}
+
+// EditChatInviteLinkConfig allows you to edit a non-primary invite link created
+// by the bot. The bot must be an administrator in the chat for this to work and
+// must have the appropriate admin rights.
+type EditChatInviteLinkConfig struct {
+ ChatConfig
+ InviteLink string
+ Name string
+ ExpireDate int
+ MemberLimit int
+ CreatesJoinRequest bool
+}
+
+func (EditChatInviteLinkConfig) method() string {
+ return "editChatInviteLink"
+}
+
+func (config EditChatInviteLinkConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername)
+ params.AddNonEmpty("name", config.Name)
+ params["invite_link"] = config.InviteLink
+ params.AddNonZero("expire_date", config.ExpireDate)
+ params.AddNonZero("member_limit", config.MemberLimit)
+ params.AddBool("creates_join_request", config.CreatesJoinRequest)
+
+ return params, nil
+}
+
+// RevokeChatInviteLinkConfig allows you to revoke an invite link created by the
+// bot. If the primary link is revoked, a new link is automatically generated.
+// The bot must be an administrator in the chat for this to work and must have
+// the appropriate admin rights.
+type RevokeChatInviteLinkConfig struct {
+ ChatConfig
+ InviteLink string
+}
+
+func (RevokeChatInviteLinkConfig) method() string {
+ return "revokeChatInviteLink"
+}
+
+func (config RevokeChatInviteLinkConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername)
+ params["invite_link"] = config.InviteLink
+
+ return params, nil
+}
+
+// ApproveChatJoinRequestConfig allows you to approve a chat join request.
+type ApproveChatJoinRequestConfig struct {
+ ChatConfig
+ UserID int64
+}
+
+func (ApproveChatJoinRequestConfig) method() string {
+ return "approveChatJoinRequest"
+}
+
+func (config ApproveChatJoinRequestConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername)
+ params.AddNonZero("user_id", int(config.UserID))
+
+ return params, nil
+}
+
+// DeclineChatJoinRequest allows you to decline a chat join request.
+type DeclineChatJoinRequest struct {
+ ChatConfig
+ UserID int64
+}
+
+func (DeclineChatJoinRequest) method() string {
+ return "declineChatJoinRequest"
+}
+
+func (config DeclineChatJoinRequest) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername)
+ params.AddNonZero("user_id", int(config.UserID))
+
+ return params, nil
+}
+
+// LeaveChatConfig allows you to leave a chat.
+type LeaveChatConfig struct {
+ ChatID int64
+ ChannelUsername string
+}
+
+func (config LeaveChatConfig) method() string {
+ return "leaveChat"
+}
+
+func (config LeaveChatConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
+
+ return params, nil
+}
+
+// ChatConfigWithUser contains information about a chat and a user.
+type ChatConfigWithUser struct {
+ ChatID int64
+ SuperGroupUsername string
+ UserID int64
+}
+
+func (config ChatConfigWithUser) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername)
+ params.AddNonZero64("user_id", config.UserID)
+
+ return params, nil
+}
+
+// GetChatMemberConfig is information about getting a specific member in a chat.
+type GetChatMemberConfig struct {
+ ChatConfigWithUser
+}
+
+func (GetChatMemberConfig) method() string {
+ return "getChatMember"
+}
+
+// InvoiceConfig contains information for sendInvoice request.
+type InvoiceConfig struct {
+ BaseChat
+ Title string // required
+ Description string // required
+ Payload string // required
+ ProviderToken string // required
+ Currency string // required
+ Prices []LabeledPrice // required
+ MaxTipAmount int
+ SuggestedTipAmounts []int
+ StartParameter string
+ ProviderData string
+ PhotoURL string
+ PhotoSize int
+ PhotoWidth int
+ PhotoHeight int
+ NeedName bool
+ NeedPhoneNumber bool
+ NeedEmail bool
+ NeedShippingAddress bool
+ SendPhoneNumberToProvider bool
+ SendEmailToProvider bool
+ IsFlexible bool
+}
+
+func (config InvoiceConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
+ if err != nil {
+ return params, err
+ }
+
+ params["title"] = config.Title
+ params["description"] = config.Description
+ params["payload"] = config.Payload
+ params["provider_token"] = config.ProviderToken
+ params["currency"] = config.Currency
+ if err = params.AddInterface("prices", config.Prices); err != nil {
+ return params, err
+ }
+
+ params.AddNonZero("max_tip_amount", config.MaxTipAmount)
+ err = params.AddInterface("suggested_tip_amounts", config.SuggestedTipAmounts)
+ params.AddNonEmpty("start_parameter", config.StartParameter)
+ params.AddNonEmpty("provider_data", config.ProviderData)
+ params.AddNonEmpty("photo_url", config.PhotoURL)
+ params.AddNonZero("photo_size", config.PhotoSize)
+ params.AddNonZero("photo_width", config.PhotoWidth)
+ params.AddNonZero("photo_height", config.PhotoHeight)
+ params.AddBool("need_name", config.NeedName)
+ params.AddBool("need_phone_number", config.NeedPhoneNumber)
+ params.AddBool("need_email", config.NeedEmail)
+ params.AddBool("need_shipping_address", config.NeedShippingAddress)
+ params.AddBool("is_flexible", config.IsFlexible)
+ params.AddBool("send_phone_number_to_provider", config.SendPhoneNumberToProvider)
+ params.AddBool("send_email_to_provider", config.SendEmailToProvider)
+
+ return params, err
+}
+
+func (config InvoiceConfig) method() string {
+ return "sendInvoice"
+}
+
+// ShippingConfig contains information for answerShippingQuery request.
+type ShippingConfig struct {
+ ShippingQueryID string // required
+ OK bool // required
+ ShippingOptions []ShippingOption
+ ErrorMessage string
+}
+
+func (config ShippingConfig) method() string {
+ return "answerShippingQuery"
+}
+
+func (config ShippingConfig) params() (Params, error) {
+ params := make(Params)
+
+ params["shipping_query_id"] = config.ShippingQueryID
+ params.AddBool("ok", config.OK)
+ err := params.AddInterface("shipping_options", config.ShippingOptions)
+ params.AddNonEmpty("error_message", config.ErrorMessage)
+
+ return params, err
+}
+
+// PreCheckoutConfig conatins information for answerPreCheckoutQuery request.
+type PreCheckoutConfig struct {
+ PreCheckoutQueryID string // required
+ OK bool // required
+ ErrorMessage string
+}
+
+func (config PreCheckoutConfig) method() string {
+ return "answerPreCheckoutQuery"
+}
+
+func (config PreCheckoutConfig) params() (Params, error) {
+ params := make(Params)
+
+ params["pre_checkout_query_id"] = config.PreCheckoutQueryID
+ params.AddBool("ok", config.OK)
+ params.AddNonEmpty("error_message", config.ErrorMessage)
+
+ return params, nil
+}
+
+// DeleteMessageConfig contains information of a message in a chat to delete.
+type DeleteMessageConfig struct {
+ ChannelUsername string
+ ChatID int64
+ MessageID int
+}
+
+func (config DeleteMessageConfig) method() string {
+ return "deleteMessage"
+}
+
+func (config DeleteMessageConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
+ params.AddNonZero("message_id", config.MessageID)
+
+ return params, nil
+}
+
+// PinChatMessageConfig contains information of a message in a chat to pin.
+type PinChatMessageConfig struct {
+ ChatID int64
+ ChannelUsername string
+ MessageID int
+ DisableNotification bool
+}
+
+func (config PinChatMessageConfig) method() string {
+ return "pinChatMessage"
+}
+
+func (config PinChatMessageConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
+ params.AddNonZero("message_id", config.MessageID)
+ params.AddBool("disable_notification", config.DisableNotification)
+
+ return params, nil
+}
+
+// UnpinChatMessageConfig contains information of a chat message to unpin.
+//
+// If MessageID is not specified, it will unpin the most recent pin.
+type UnpinChatMessageConfig struct {
+ ChatID int64
+ ChannelUsername string
+ MessageID int
}
-// name returns the field name for the Photo.
-func (config SetChatPhotoConfig) name() string {
- return "photo"
+func (config UnpinChatMessageConfig) method() string {
+ return "unpinChatMessage"
+}
+
+func (config UnpinChatMessageConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
+ params.AddNonZero("message_id", config.MessageID)
+
+ return params, nil
+}
+
+// UnpinAllChatMessagesConfig contains information of all messages to unpin in
+// a chat.
+type UnpinAllChatMessagesConfig struct {
+ ChatID int64
+ ChannelUsername string
+}
+
+func (config UnpinAllChatMessagesConfig) method() string {
+ return "unpinAllChatMessages"
+}
+
+func (config UnpinAllChatMessagesConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
+
+ return params, nil
+}
+
+// SetChatPhotoConfig allows you to set a group, supergroup, or channel's photo.
+type SetChatPhotoConfig struct {
+ BaseFile
}
-// method returns Telegram API method name for sending Photo.
func (config SetChatPhotoConfig) method() string {
return "setChatPhoto"
}
-// DeleteChatPhotoConfig contains information for delete chat photo.
+func (config SetChatPhotoConfig) files() []RequestFile {
+ return []RequestFile{{
+ Name: "photo",
+ Data: config.File,
+ }}
+}
+
+// DeleteChatPhotoConfig allows you to delete a group, supergroup, or channel's photo.
type DeleteChatPhotoConfig struct {
- ChatID int64
+ ChatID int64
+ ChannelUsername string
}
func (config DeleteChatPhotoConfig) method() string {
return "deleteChatPhoto"
}
-func (config DeleteChatPhotoConfig) values() (url.Values, error) {
- v := url.Values{}
+func (config DeleteChatPhotoConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
+
+ return params, nil
+}
- v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
+// SetChatTitleConfig allows you to set the title of something other than a private chat.
+type SetChatTitleConfig struct {
+ ChatID int64
+ ChannelUsername string
- return v, nil
+ Title string
}
-// GetStickerSetConfig contains information for get sticker set.
+func (config SetChatTitleConfig) method() string {
+ return "setChatTitle"
+}
+
+func (config SetChatTitleConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
+ params["title"] = config.Title
+
+ return params, nil
+}
+
+// SetChatDescriptionConfig allows you to set the description of a supergroup or channel.
+type SetChatDescriptionConfig struct {
+ ChatID int64
+ ChannelUsername string
+
+ Description string
+}
+
+func (config SetChatDescriptionConfig) method() string {
+ return "setChatDescription"
+}
+
+func (config SetChatDescriptionConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
+ params["description"] = config.Description
+
+ return params, nil
+}
+
+// GetStickerSetConfig allows you to get the stickers in a set.
type GetStickerSetConfig struct {
Name string
}
@@ -1332,35 +1922,498 @@ func (config GetStickerSetConfig) method() string {
return "getStickerSet"
}
-func (config GetStickerSetConfig) values() (url.Values, error) {
- v := url.Values{}
- v.Add("name", config.Name)
- return v, nil
+func (config GetStickerSetConfig) params() (Params, error) {
+ params := make(Params)
+
+ params["name"] = config.Name
+
+ return params, nil
+}
+
+// UploadStickerConfig allows you to upload a sticker for use in a set later.
+type UploadStickerConfig struct {
+ UserID int64
+ PNGSticker RequestFileData
+}
+
+func (config UploadStickerConfig) method() string {
+ return "uploadStickerFile"
+}
+
+func (config UploadStickerConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddNonZero64("user_id", config.UserID)
+
+ return params, nil
+}
+
+func (config UploadStickerConfig) files() []RequestFile {
+ return []RequestFile{{
+ Name: "png_sticker",
+ Data: config.PNGSticker,
+ }}
+}
+
+// NewStickerSetConfig allows creating a new sticker set.
+//
+// You must set either PNGSticker or TGSSticker.
+type NewStickerSetConfig struct {
+ UserID int64
+ Name string
+ Title string
+ PNGSticker RequestFileData
+ TGSSticker RequestFileData
+ Emojis string
+ ContainsMasks bool
+ MaskPosition *MaskPosition
+}
+
+func (config NewStickerSetConfig) method() string {
+ return "createNewStickerSet"
+}
+
+func (config NewStickerSetConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddNonZero64("user_id", config.UserID)
+ params["name"] = config.Name
+ params["title"] = config.Title
+
+ params["emojis"] = config.Emojis
+
+ params.AddBool("contains_masks", config.ContainsMasks)
+
+ err := params.AddInterface("mask_position", config.MaskPosition)
+
+ return params, err
+}
+
+func (config NewStickerSetConfig) files() []RequestFile {
+ if config.PNGSticker != nil {
+ return []RequestFile{{
+ Name: "png_sticker",
+ Data: config.PNGSticker,
+ }}
+ }
+
+ return []RequestFile{{
+ Name: "tgs_sticker",
+ Data: config.TGSSticker,
+ }}
+}
+
+// AddStickerConfig allows you to add a sticker to a set.
+type AddStickerConfig struct {
+ UserID int64
+ Name string
+ PNGSticker RequestFileData
+ TGSSticker RequestFileData
+ Emojis string
+ MaskPosition *MaskPosition
+}
+
+func (config AddStickerConfig) method() string {
+ return "addStickerToSet"
+}
+
+func (config AddStickerConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddNonZero64("user_id", config.UserID)
+ params["name"] = config.Name
+ params["emojis"] = config.Emojis
+
+ err := params.AddInterface("mask_position", config.MaskPosition)
+
+ return params, err
+}
+
+func (config AddStickerConfig) files() []RequestFile {
+ if config.PNGSticker != nil {
+ return []RequestFile{{
+ Name: "png_sticker",
+ Data: config.PNGSticker,
+ }}
+ }
+
+ return []RequestFile{{
+ Name: "tgs_sticker",
+ Data: config.TGSSticker,
+ }}
+
+}
+
+// SetStickerPositionConfig allows you to change the position of a sticker in a set.
+type SetStickerPositionConfig struct {
+ Sticker string
+ Position int
+}
+
+func (config SetStickerPositionConfig) method() string {
+ return "setStickerPositionInSet"
+}
+
+func (config SetStickerPositionConfig) params() (Params, error) {
+ params := make(Params)
+
+ params["sticker"] = config.Sticker
+ params.AddNonZero("position", config.Position)
+
+ return params, nil
+}
+
+// DeleteStickerConfig allows you to delete a sticker from a set.
+type DeleteStickerConfig struct {
+ Sticker string
+}
+
+func (config DeleteStickerConfig) method() string {
+ return "deleteStickerFromSet"
+}
+
+func (config DeleteStickerConfig) params() (Params, error) {
+ params := make(Params)
+
+ params["sticker"] = config.Sticker
+
+ return params, nil
+}
+
+// SetStickerSetThumbConfig allows you to set the thumbnail for a sticker set.
+type SetStickerSetThumbConfig struct {
+ Name string
+ UserID int64
+ Thumb RequestFileData
+}
+
+func (config SetStickerSetThumbConfig) method() string {
+ return "setStickerSetThumb"
+}
+
+func (config SetStickerSetThumbConfig) params() (Params, error) {
+ params := make(Params)
+
+ params["name"] = config.Name
+ params.AddNonZero64("user_id", config.UserID)
+
+ return params, nil
+}
+
+func (config SetStickerSetThumbConfig) files() []RequestFile {
+ return []RequestFile{{
+ Name: "thumb",
+ Data: config.Thumb,
+ }}
+}
+
+// SetChatStickerSetConfig allows you to set the sticker set for a supergroup.
+type SetChatStickerSetConfig struct {
+ ChatID int64
+ SuperGroupUsername string
+
+ StickerSetName string
+}
+
+func (config SetChatStickerSetConfig) method() string {
+ return "setChatStickerSet"
+}
+
+func (config SetChatStickerSetConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername)
+ params["sticker_set_name"] = config.StickerSetName
+
+ return params, nil
+}
+
+// DeleteChatStickerSetConfig allows you to remove a supergroup's sticker set.
+type DeleteChatStickerSetConfig struct {
+ ChatID int64
+ SuperGroupUsername string
+}
+
+func (config DeleteChatStickerSetConfig) method() string {
+ return "deleteChatStickerSet"
+}
+
+func (config DeleteChatStickerSetConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername)
+
+ return params, nil
+}
+
+// MediaGroupConfig allows you to send a group of media.
+//
+// Media consist of InputMedia items (InputMediaPhoto, InputMediaVideo).
+type MediaGroupConfig struct {
+ ChatID int64
+ ChannelUsername string
+
+ Media []interface{}
+ DisableNotification bool
+ ReplyToMessageID int
+}
+
+func (config MediaGroupConfig) method() string {
+ return "sendMediaGroup"
+}
+
+func (config MediaGroupConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
+ params.AddBool("disable_notification", config.DisableNotification)
+ params.AddNonZero("reply_to_message_id", config.ReplyToMessageID)
+
+ err := params.AddInterface("media", prepareInputMediaForParams(config.Media))
+
+ return params, err
+}
+
+func (config MediaGroupConfig) files() []RequestFile {
+ return prepareInputMediaForFiles(config.Media)
}
// DiceConfig contains information about a sendDice request.
type DiceConfig struct {
BaseChat
// Emoji on which the dice throw animation is based.
- // Currently, must be one of “🎲”, “🎯”, or “🏀”.
- // Dice can have values 1-6 for “🎲” and “🎯”, and values 1-5 for “🏀”.
+ // Currently, must be one of 🎲, 🎯, 🏀, ⚽, 🎳, or 🎰.
+ // Dice can have values 1-6 for 🎲, 🎯, and 🎳, values 1-5 for 🏀 and ⚽,
+ // and values 1-64 for 🎰.
// Defaults to “🎲”
Emoji string
}
-// values returns a url.Values representation of DiceConfig.
-func (config DiceConfig) values() (url.Values, error) {
- v, err := config.BaseChat.values()
+func (config DiceConfig) method() string {
+ return "sendDice"
+}
+
+func (config DiceConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
if err != nil {
- return v, err
+ return params, err
}
- if config.Emoji != "" {
- v.Add("emoji", config.Emoji)
+
+ params.AddNonEmpty("emoji", config.Emoji)
+
+ return params, err
+}
+
+// GetMyCommandsConfig gets a list of the currently registered commands.
+type GetMyCommandsConfig struct {
+ Scope *BotCommandScope
+ LanguageCode string
+}
+
+func (config GetMyCommandsConfig) method() string {
+ return "getMyCommands"
+}
+
+func (config GetMyCommandsConfig) params() (Params, error) {
+ params := make(Params)
+
+ err := params.AddInterface("scope", config.Scope)
+ params.AddNonEmpty("language_code", config.LanguageCode)
+
+ return params, err
+}
+
+// SetMyCommandsConfig sets a list of commands the bot understands.
+type SetMyCommandsConfig struct {
+ Commands []BotCommand
+ Scope *BotCommandScope
+ LanguageCode string
+}
+
+func (config SetMyCommandsConfig) method() string {
+ return "setMyCommands"
+}
+
+func (config SetMyCommandsConfig) params() (Params, error) {
+ params := make(Params)
+
+ if err := params.AddInterface("commands", config.Commands); err != nil {
+ return params, err
}
- return v, nil
+ err := params.AddInterface("scope", config.Scope)
+ params.AddNonEmpty("language_code", config.LanguageCode)
+
+ return params, err
}
-// method returns Telegram API method name for sending Dice.
-func (config DiceConfig) method() string {
- return "sendDice"
+type DeleteMyCommandsConfig struct {
+ Scope *BotCommandScope
+ LanguageCode string
+}
+
+func (config DeleteMyCommandsConfig) method() string {
+ return "deleteMyCommands"
+}
+
+func (config DeleteMyCommandsConfig) params() (Params, error) {
+ params := make(Params)
+
+ err := params.AddInterface("scope", config.Scope)
+ params.AddNonEmpty("language_code", config.LanguageCode)
+
+ return params, err
+}
+
+// prepareInputMediaParam evaluates a single InputMedia and determines if it
+// needs to be modified for a successful upload. If it returns nil, then the
+// value does not need to be included in the params. Otherwise, it will return
+// the same type as was originally provided.
+//
+// The idx is used to calculate the file field name. If you only have a single
+// file, 0 may be used. It is formatted into "attach://file-%d" for the primary
+// media and "attach://file-%d-thumb" for thumbnails.
+//
+// It is expected to be used in conjunction with prepareInputMediaFile.
+func prepareInputMediaParam(inputMedia interface{}, idx int) interface{} {
+ switch m := inputMedia.(type) {
+ case InputMediaPhoto:
+ if m.Media.NeedsUpload() {
+ m.Media = fileAttach(fmt.Sprintf("attach://file-%d", idx))
+ }
+
+ return m
+ case InputMediaVideo:
+ if m.Media.NeedsUpload() {
+ m.Media = fileAttach(fmt.Sprintf("attach://file-%d", idx))
+ }
+
+ if m.Thumb != nil && m.Thumb.NeedsUpload() {
+ m.Thumb = fileAttach(fmt.Sprintf("attach://file-%d-thumb", idx))
+ }
+
+ return m
+ case InputMediaAudio:
+ if m.Media.NeedsUpload() {
+ m.Media = fileAttach(fmt.Sprintf("attach://file-%d", idx))
+ }
+
+ if m.Thumb != nil && m.Thumb.NeedsUpload() {
+ m.Thumb = fileAttach(fmt.Sprintf("attach://file-%d-thumb", idx))
+ }
+
+ return m
+ case InputMediaDocument:
+ if m.Media.NeedsUpload() {
+ m.Media = fileAttach(fmt.Sprintf("attach://file-%d", idx))
+ }
+
+ if m.Thumb != nil && m.Thumb.NeedsUpload() {
+ m.Thumb = fileAttach(fmt.Sprintf("attach://file-%d-thumb", idx))
+ }
+
+ return m
+ }
+
+ return nil
+}
+
+// prepareInputMediaFile generates an array of RequestFile to provide for
+// Fileable's files method. It returns an array as a single InputMedia may have
+// multiple files, for the primary media and a thumbnail.
+//
+// The idx parameter is used to generate file field names. It uses the names
+// "file-%d" for the main file and "file-%d-thumb" for the thumbnail.
+//
+// It is expected to be used in conjunction with prepareInputMediaParam.
+func prepareInputMediaFile(inputMedia interface{}, idx int) []RequestFile {
+ files := []RequestFile{}
+
+ switch m := inputMedia.(type) {
+ case InputMediaPhoto:
+ if m.Media.NeedsUpload() {
+ files = append(files, RequestFile{
+ Name: fmt.Sprintf("file-%d", idx),
+ Data: m.Media,
+ })
+ }
+ case InputMediaVideo:
+ if m.Media.NeedsUpload() {
+ files = append(files, RequestFile{
+ Name: fmt.Sprintf("file-%d", idx),
+ Data: m.Media,
+ })
+ }
+
+ if m.Thumb != nil && m.Thumb.NeedsUpload() {
+ files = append(files, RequestFile{
+ Name: fmt.Sprintf("file-%d", idx),
+ Data: m.Thumb,
+ })
+ }
+ case InputMediaDocument:
+ if m.Media.NeedsUpload() {
+ files = append(files, RequestFile{
+ Name: fmt.Sprintf("file-%d", idx),
+ Data: m.Media,
+ })
+ }
+
+ if m.Thumb != nil && m.Thumb.NeedsUpload() {
+ files = append(files, RequestFile{
+ Name: fmt.Sprintf("file-%d", idx),
+ Data: m.Thumb,
+ })
+ }
+ case InputMediaAudio:
+ if m.Media.NeedsUpload() {
+ files = append(files, RequestFile{
+ Name: fmt.Sprintf("file-%d", idx),
+ Data: m.Media,
+ })
+ }
+
+ if m.Thumb != nil && m.Thumb.NeedsUpload() {
+ files = append(files, RequestFile{
+ Name: fmt.Sprintf("file-%d", idx),
+ Data: m.Thumb,
+ })
+ }
+ }
+
+ return files
+}
+
+// prepareInputMediaForParams calls prepareInputMediaParam for each item
+// provided and returns a new array with the correct params for a request.
+//
+// It is expected that files will get data from the associated function,
+// prepareInputMediaForFiles.
+func prepareInputMediaForParams(inputMedia []interface{}) []interface{} {
+ newMedia := make([]interface{}, len(inputMedia))
+ copy(newMedia, inputMedia)
+
+ for idx, media := range inputMedia {
+ if param := prepareInputMediaParam(media, idx); param != nil {
+ newMedia[idx] = param
+ }
+ }
+
+ return newMedia
+}
+
+// prepareInputMediaForFiles calls prepareInputMediaFile for each item
+// provided and returns a new array with the correct files for a request.
+//
+// It is expected that params will get data from the associated function,
+// prepareInputMediaForParams.
+func prepareInputMediaForFiles(inputMedia []interface{}) []RequestFile {
+ files := []RequestFile{}
+
+ for idx, media := range inputMedia {
+ if file := prepareInputMediaFile(media, idx); file != nil {
+ files = append(files, file...)
+ }
+ }
+
+ return files
}
diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md
new file mode 100644
index 00000000..81e38375
--- /dev/null
+++ b/docs/SUMMARY.md
@@ -0,0 +1,17 @@
+# Summary
+
+- [Getting Started](./getting-started/README.md)
+ - [Library Structure](./getting-started/library-structure.md)
+ - [Files](./getting-started/files.md)
+ - [Important Notes](./getting-started/important-notes.md)
+- [Examples](./examples/README.md)
+ - [Command Handling](./examples/command-handling.md)
+ - [Keyboard](./examples/keyboard.md)
+ - [Inline Keyboard](./examples/inline-keyboard.md)
+- [Change Log](./changelog.md)
+
+# Contributing
+
+- [Internals](./internals/README.md)
+ - [Adding Endpoints](./internals/adding-endpoints.md)
+ - [Uploading Files](./internals/uploading-files.md)
diff --git a/docs/changelog.md b/docs/changelog.md
new file mode 100644
index 00000000..83fffa1a
--- /dev/null
+++ b/docs/changelog.md
@@ -0,0 +1,19 @@
+# Change Log
+
+## v5
+
+**Work In Progress**
+
+- Remove all methods that return `(APIResponse, error)`.
+ - Use the `Request` method instead.
+ - For more information, see [Library Structure][library-structure].
+- Remove all `New*Upload` and `New*Share` methods, replace with `New*`.
+ - Use different [file types][files] to specify if upload or share.
+- Rename `UploadFile` to `UploadFiles`, accept `[]RequestFile` instead of a
+ single fieldname and file.
+- Fix methods returning `APIResponse` and errors to always use pointers.
+- Update user IDs to `int64` because of Bot API changes.
+- Add missing Bot API features.
+
+[library-structure]: ./getting-started/library-structure.md#methods
+[files]: ./getting-started/files.md
diff --git a/docs/examples/README.md b/docs/examples/README.md
new file mode 100644
index 00000000..8a6157c3
--- /dev/null
+++ b/docs/examples/README.md
@@ -0,0 +1,4 @@
+# Examples
+
+With a better understanding of how the library works, let's look at some more
+examples showing off some of Telegram's features.
diff --git a/docs/examples/command-handling.md b/docs/examples/command-handling.md
new file mode 100644
index 00000000..d1d8b29d
--- /dev/null
+++ b/docs/examples/command-handling.md
@@ -0,0 +1,60 @@
+# Command Handling
+
+This is a simple example of changing behavior based on a provided command.
+
+```go
+package main
+
+import (
+ "log"
+ "os"
+
+ tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
+)
+
+func main() {
+ bot, err := tgbotapi.NewBotAPI(os.Getenv("TELEGRAM_APITOKEN"))
+ if err != nil {
+ log.Panic(err)
+ }
+
+ bot.Debug = true
+
+ log.Printf("Authorized on account %s", bot.Self.UserName)
+
+ u := tgbotapi.NewUpdate(0)
+ u.Timeout = 60
+
+ updates := bot.GetUpdatesChan(u)
+
+ for update := range updates {
+ if update.Message == nil { // ignore any non-Message updates
+ continue
+ }
+
+ if !update.Message.IsCommand() { // ignore any non-command Messages
+ continue
+ }
+
+ // Create a new MessageConfig. We don't have text yet,
+ // so we leave it empty.
+ msg := tgbotapi.NewMessage(update.Message.Chat.ID, "")
+
+ // Extract the command from the Message.
+ switch update.Message.Command() {
+ case "help":
+ msg.Text = "I understand /sayhi and /status."
+ case "sayhi":
+ msg.Text = "Hi :)"
+ case "status":
+ msg.Text = "I'm ok."
+ default:
+ msg.Text = "I don't know that command"
+ }
+
+ if _, err := bot.Send(msg); err != nil {
+ log.Panic(err)
+ }
+ }
+}
+```
diff --git a/docs/examples/inline-keyboard.md b/docs/examples/inline-keyboard.md
new file mode 100644
index 00000000..e14ee631
--- /dev/null
+++ b/docs/examples/inline-keyboard.md
@@ -0,0 +1,80 @@
+# Inline Keyboard
+
+This bot waits for you to send it the message "open" before sending you an
+inline keyboard containing a URL and some numbers. When a number is clicked, it
+sends you a message with your selected number.
+
+```go
+package main
+
+import (
+ "log"
+ "os"
+
+ tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
+)
+
+var numericKeyboard = tgbotapi.NewInlineKeyboardMarkup(
+ tgbotapi.NewInlineKeyboardRow(
+ tgbotapi.NewInlineKeyboardButtonURL("1.com", "http://1.com"),
+ tgbotapi.NewInlineKeyboardButtonData("2", "2"),
+ tgbotapi.NewInlineKeyboardButtonData("3", "3"),
+ ),
+ tgbotapi.NewInlineKeyboardRow(
+ tgbotapi.NewInlineKeyboardButtonData("4", "4"),
+ tgbotapi.NewInlineKeyboardButtonData("5", "5"),
+ tgbotapi.NewInlineKeyboardButtonData("6", "6"),
+ ),
+)
+
+func main() {
+ bot, err := tgbotapi.NewBotAPI(os.Getenv("TELEGRAM_APITOKEN"))
+ if err != nil {
+ log.Panic(err)
+ }
+
+ bot.Debug = true
+
+ log.Printf("Authorized on account %s", bot.Self.UserName)
+
+ u := tgbotapi.NewUpdate(0)
+ u.Timeout = 60
+
+ updates := bot.GetUpdatesChan(u)
+
+ // Loop through each update.
+ for update := range updates {
+ // Check if we've gotten a message update.
+ if update.Message != nil {
+ // Construct a new message from the given chat ID and containing
+ // the text that we received.
+ msg := tgbotapi.NewMessage(update.Message.Chat.ID, update.Message.Text)
+
+ // If the message was open, add a copy of our numeric keyboard.
+ switch update.Message.Text {
+ case "open":
+ msg.ReplyMarkup = numericKeyboard
+
+ }
+
+ // Send the message.
+ if _, err = bot.Send(msg); err != nil {
+ panic(err)
+ }
+ } else if update.CallbackQuery != nil {
+ // Respond to the callback query, telling Telegram to show the user
+ // a message with the data received.
+ callback := tgbotapi.NewCallback(update.CallbackQuery.ID, update.CallbackQuery.Data)
+ if _, err := bot.Request(callback); err != nil {
+ panic(err)
+ }
+
+ // And finally, send a message containing the data received.
+ msg := tgbotapi.NewMessage(update.CallbackQuery.Message.Chat.ID, update.CallbackQuery.Data)
+ if _, err := bot.Send(msg); err != nil {
+ panic(err)
+ }
+ }
+ }
+}
+```
diff --git a/docs/examples/keyboard.md b/docs/examples/keyboard.md
new file mode 100644
index 00000000..d67a9154
--- /dev/null
+++ b/docs/examples/keyboard.md
@@ -0,0 +1,63 @@
+# Keyboard
+
+This bot shows a numeric keyboard when you send a "open" message and hides it
+when you send "close" message.
+
+```go
+package main
+
+import (
+ "log"
+ "os"
+
+ tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
+)
+
+var numericKeyboard = tgbotapi.NewReplyKeyboard(
+ tgbotapi.NewKeyboardButtonRow(
+ tgbotapi.NewKeyboardButton("1"),
+ tgbotapi.NewKeyboardButton("2"),
+ tgbotapi.NewKeyboardButton("3"),
+ ),
+ tgbotapi.NewKeyboardButtonRow(
+ tgbotapi.NewKeyboardButton("4"),
+ tgbotapi.NewKeyboardButton("5"),
+ tgbotapi.NewKeyboardButton("6"),
+ ),
+)
+
+func main() {
+ bot, err := tgbotapi.NewBotAPI(os.Getenv("TELEGRAM_APITOKEN"))
+ if err != nil {
+ log.Panic(err)
+ }
+
+ bot.Debug = true
+
+ log.Printf("Authorized on account %s", bot.Self.UserName)
+
+ u := tgbotapi.NewUpdate(0)
+ u.Timeout = 60
+
+ updates := bot.GetUpdatesChan(u)
+
+ for update := range updates {
+ if update.Message == nil { // ignore non-Message updates
+ continue
+ }
+
+ msg := tgbotapi.NewMessage(update.Message.Chat.ID, update.Message.Text)
+
+ switch update.Message.Text {
+ case "open":
+ msg.ReplyMarkup = numericKeyboard
+ case "close":
+ msg.ReplyMarkup = tgbotapi.NewRemoveKeyboard(true)
+ }
+
+ if _, err := bot.Send(msg); err != nil {
+ log.Panic(err)
+ }
+ }
+}
+```
diff --git a/docs/getting-started/README.md b/docs/getting-started/README.md
new file mode 100644
index 00000000..25c0f924
--- /dev/null
+++ b/docs/getting-started/README.md
@@ -0,0 +1,112 @@
+# Getting Started
+
+This library is designed as a simple wrapper around the Telegram Bot API.
+It's encouraged to read [Telegram's docs][telegram-docs] first to get an
+understanding of what Bots are capable of doing. They also provide some good
+approaches to solve common problems.
+
+[telegram-docs]: https://core.telegram.org/bots
+
+## Installing
+
+```bash
+go get -u github.com/go-telegram-bot-api/telegram-bot-api/v5@develop
+```
+
+It's currently suggested to use the develop branch. While there may be breaking
+changes, it has a number of features not yet available on master.
+
+## A Simple Bot
+
+To walk through the basics, let's create a simple echo bot that replies to your
+messages repeating what you said. Make sure you get an API token from
+[@Botfather][botfather] before continuing.
+
+Let's start by constructing a new [BotAPI][bot-api-docs].
+
+[botfather]: https://t.me/Botfather
+[bot-api-docs]: https://pkg.go.dev/github.com/go-telegram-bot-api/telegram-bot-api/v5?tab=doc#BotAPI
+
+```go
+package main
+
+import (
+ "os"
+
+ tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
+)
+
+func main() {
+ bot, err := tgbotapi.NewBotAPI(os.Getenv("TELEGRAM_APITOKEN"))
+ if err != nil {
+ panic(err)
+ }
+
+ bot.Debug = true
+}
+```
+
+Instead of typing the API token directly into the file, we're using
+environment variables. This makes it easy to configure our Bot to use the right
+account and prevents us from leaking our real token into the world. Anyone with
+your token can send and receive messages from your Bot!
+
+We've also set `bot.Debug = true` in order to get more information about the
+requests being sent to Telegram. If you run the example above, you'll see
+information about a request to the [`getMe`][get-me] endpoint. The library
+automatically calls this to ensure your token is working as expected. It also
+fills in the `Self` field in your `BotAPI` struct with information about the
+Bot.
+
+Now that we've connected to Telegram, let's start getting updates and doing
+things. We can add this code in right after the line enabling debug mode.
+
+[get-me]: https://core.telegram.org/bots/api#getme
+
+```go
+ // Create a new UpdateConfig struct with an offset of 0. Offsets are used
+ // to make sure Telegram knows we've handled previous values and we don't
+ // need them repeated.
+ updateConfig := tgbotapi.NewUpdate(0)
+
+ // Tell Telegram we should wait up to 30 seconds on each request for an
+ // update. This way we can get information just as quickly as making many
+ // frequent requests without having to send nearly as many.
+ updateConfig.Timeout = 30
+
+ // Start polling Telegram for updates.
+ updates := bot.GetUpdatesChan(updateConfig)
+
+ // Let's go through each update that we're getting from Telegram.
+ for update := range updates {
+ // Telegram can send many types of updates depending on what your Bot
+ // is up to. We only want to look at messages for now, so we can
+ // discard any other updates.
+ if update.Message == nil {
+ continue
+ }
+
+ // Now that we know we've gotten a new message, we can construct a
+ // reply! We'll take the Chat ID and Text from the incoming message
+ // and use it to create a new message.
+ msg := tgbotapi.NewMessage(update.Message.Chat.ID, update.Message.Text)
+ // We'll also say that this message is a reply to the previous message.
+ // For any other specifications than Chat ID or Text, you'll need to
+ // set fields on the `MessageConfig`.
+ msg.ReplyToMessageID = update.Message.MessageID
+
+ // Okay, we're sending our message off! We don't care about the message
+ // we just sent, so we'll discard it.
+ if _, err := bot.Send(msg); err != nil {
+ // Note that panics are a bad way to handle errors. Telegram can
+ // have service outages or network errors, you should retry sending
+ // messages or more gracefully handle failures.
+ panic(err)
+ }
+ }
+```
+
+Congradulations! You've made your very own bot!
+
+Now that you've got some of the basics down, we can start talking about how the
+library is structured and more advanced features.
diff --git a/docs/getting-started/files.md b/docs/getting-started/files.md
new file mode 100644
index 00000000..e86febd7
--- /dev/null
+++ b/docs/getting-started/files.md
@@ -0,0 +1,68 @@
+# Files
+
+Telegram supports specifying files in many different formats. In order to
+accommodate them all, there are multiple structs and type aliases required.
+
+All of these types implement the `RequestFileData` interface.
+
+| Type | Description |
+| ------------ | ------------------------------------------------------------------------- |
+| `FilePath` | A local path to a file |
+| `FileID` | Existing file ID on Telegram's servers |
+| `FileURL` | URL to file, must be served with expected MIME type |
+| `FileReader` | Use an `io.Reader` to provide a file. Lazily read to save memory. |
+| `FileBytes` | `[]byte` containing file data. Prefer to use `FileReader` to save memory. |
+
+## `FilePath`
+
+A path to a local file.
+
+```go
+file := tgbotapi.FilePath("tests/image.jpg")
+```
+
+## `FileID`
+
+An ID previously uploaded to Telegram. IDs may only be reused by the same bot
+that received them. Additionally, thumbnail IDs cannot be reused.
+
+```go
+file := tgbotapi.FileID("AgACAgIAAxkDAALesF8dCjAAAa_…")
+```
+
+## `FileURL`
+
+A URL to an existing resource. It must be served with a correct MIME type to
+work as expected.
+
+```go
+file := tgbotapi.FileURL("https://i.imgur.com/unQLJIb.jpg")
+```
+
+## `FileReader`
+
+Use an `io.Reader` to provide file contents as needed. Requires a filename for
+the virtual file.
+
+```go
+var reader io.Reader
+
+file := tgbotapi.FileReader{
+ Name: "image.jpg",
+ Reader: reader,
+}
+```
+
+## `FileBytes`
+
+Use a `[]byte` to provide file contents. Generally try to avoid this as it
+results in high memory usage. Also requires a filename for the virtual file.
+
+```go
+var data []byte
+
+file := tgbotapi.FileBytes{
+ Name: "image.jpg",
+ Bytes: data,
+}
+```
diff --git a/docs/getting-started/important-notes.md b/docs/getting-started/important-notes.md
new file mode 100644
index 00000000..d6388aa4
--- /dev/null
+++ b/docs/getting-started/important-notes.md
@@ -0,0 +1,56 @@
+# Important Notes
+
+The Telegram Bot API has a few potentially unanticipated behaviors. Here are a
+few of them. If any behavior was surprising to you, please feel free to open a
+pull request!
+
+## Callback Queries
+
+- Every callback query must be answered, even if there is nothing to display to
+ the user. Failure to do so will show a loading icon on the keyboard until the
+ operation times out.
+
+## ChatMemberUpdated
+
+- In order to receive `ChatMember` updates, you must explicitly add
+ `UpdateTypeChatMember` to your `AllowedUpdates` when getting updates or
+ setting your webhook.
+
+## Entities use UTF16
+
+- When extracting text entities using offsets and lengths, characters can appear
+ to be in incorrect positions. This is because Telegram uses UTF16 lengths
+ while Golang uses UTF8. It's possible to convert between the two, see
+ [issue #231][issue-231] for more details.
+
+[issue-231]: https://github.com/go-telegram-bot-api/telegram-bot-api/issues/231
+
+## GetUpdatesChan
+
+- This method is very basic and likely unsuitable for production use. Consider
+ creating your own implementation instead, as it's very simple to replicate.
+- This method only allows your bot to process one update at a time. You can
+ spawn goroutines to handle updates concurrently or switch to webhooks instead.
+ Webhooks are suggested for high traffic bots.
+
+## Nil Updates
+
+- At most one of the fields in an `Update` will be set to a non-nil value. When
+ evaluating updates, you must make sure you check that the field is not nil
+ before trying to access any of it's fields.
+
+## Privacy Mode
+
+- By default, bots only get updates directly addressed to them. If you need to
+ get all messages, you must disable privacy mode with Botfather. Bots already
+ added to groups will need to be removed and readded for the changes to take
+ effect. You can read more on the [Telegram Bot API docs][api-docs].
+
+[api-docs]: https://core.telegram.org/bots/faq#what-messages-will-my-bot-get
+
+## User and Chat ID size
+
+- These types require up to 52 significant bits to store correctly, making a
+ 64-bit integer type required in most languages. They are already `int64` types
+ in this library, but make sure you use correct types when saving them to a
+ database or passing them to another language.
diff --git a/docs/getting-started/library-structure.md b/docs/getting-started/library-structure.md
new file mode 100644
index 00000000..56ea6df2
--- /dev/null
+++ b/docs/getting-started/library-structure.md
@@ -0,0 +1,37 @@
+# Library Structure
+
+This library is generally broken into three components you need to understand.
+
+## Configs
+
+Configs are collections of fields related to a single request. For example, if
+one wanted to use the `sendMessage` endpoint, you could use the `MessageConfig`
+struct to configure the request. There is a one-to-one relationship between
+Telegram endpoints and configs. They generally have the naming pattern of
+removing the `send` prefix and they all end with the `Config` suffix. They
+generally implement the `Chattable` interface. If they can send files, they
+implement the `Fileable` interface.
+
+## Helpers
+
+Helpers are easier ways of constructing common Configs. Instead of having to
+create a `MessageConfig` struct and remember to set the `ChatID` and `Text`,
+you can use the `NewMessage` helper method. It takes the two required parameters
+for the request to succeed. You can then set fields on the resulting
+`MessageConfig` after it's creation. They are generally named the same as
+method names except with `send` replaced with `New`.
+
+## Methods
+
+Methods are used to send Configs after they are constructed. Generally,
+`Request` is the lowest level method you'll have to call. It accepts a
+`Chattable` parameter and knows how to upload files if needed. It returns an
+`APIResponse`, the most general return type from the Bot API. This method is
+called for any endpoint that doesn't have a more specific return type. For
+example, `setWebhook` only returns `true` or an error. Other methods may have
+more specific return types. The `getFile` endpoint returns a `File`. Almost
+every other method returns a `Message`, which you can use `Send` to obtain.
+
+There's lower level methods such as `MakeRequest` which require an endpoint and
+parameters instead of accepting configs. These are primarily used internally.
+If you find yourself having to use them, please open an issue.
diff --git a/docs/internals/README.md b/docs/internals/README.md
new file mode 100644
index 00000000..1d7db5d2
--- /dev/null
+++ b/docs/internals/README.md
@@ -0,0 +1,4 @@
+# Internals
+
+If you want to contribute to the project, here's some more information about
+the internal structure of the library.
diff --git a/docs/internals/adding-endpoints.md b/docs/internals/adding-endpoints.md
new file mode 100644
index 00000000..c4ff59a6
--- /dev/null
+++ b/docs/internals/adding-endpoints.md
@@ -0,0 +1,197 @@
+# Adding Endpoints
+
+This is mostly useful if you've managed to catch a new Telegram Bot API update
+before the library can get updated. It's also a great source of information
+about how the types work internally.
+
+## Creating the Config
+
+The first step in adding a new endpoint is to create a new Config type for it.
+These belong in `configs.go`.
+
+Let's try and add the `deleteMessage` endpoint. We can see it requires two
+fields; `chat_id` and `message_id`. We can create a struct for these.
+
+```go
+type DeleteMessageConfig struct {
+ ChatID ???
+ MessageID int
+}
+```
+
+What type should `ChatID` be? Telegram allows specifying numeric chat IDs or
+channel usernames. Golang doesn't have union types, and interfaces are entirely
+untyped. This library solves this by adding two fields, a `ChatID` and a
+`ChannelUsername`. We can now write the struct as follows.
+
+```go
+type DeleteMessageConfig struct {
+ ChannelUsername string
+ ChatID int64
+ MessageID int
+}
+```
+
+Note that `ChatID` is an `int64`. Telegram chat IDs can be greater than 32 bits.
+
+Okay, we now have our struct. But we can't send it yet. It doesn't implement
+`Chattable` so it won't work with `Request` or `Send`.
+
+### Making it `Chattable`
+
+We can see that `Chattable` only requires a few methods.
+
+```go
+type Chattable interface {
+ params() (Params, error)
+ method() string
+}
+```
+
+`params` is the fields associated with the request. `method` is the endpoint
+that this Config is associated with.
+
+Implementing the `method` is easy, so let's start with that.
+
+```go
+func (config DeleteMessageConfig) method() string {
+ return "deleteMessage"
+}
+```
+
+Now we have to add the `params`. The `Params` type is an alias for
+`map[string]string`. Telegram expects only a single field for `chat_id`, so we
+have to determine what data to send.
+
+We could use an if statement to determine which field to get the value from.
+However, as this is a relatively common operation, there's helper methods for
+`Params`. We can use the `AddFirstValid` method to go through each possible
+value and stop when it discovers a valid one. Before writing your own Config,
+it's worth taking a look through `params.go` to see what other helpers exist.
+
+Now we can take a look at what a completed `params` method looks like.
+
+```go
+func (config DeleteMessageConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
+ params.AddNonZero("message_id", config.MessageID)
+
+ return params, nil
+}
+```
+
+### Uploading Files
+
+Let's imagine that for some reason deleting a message requires a document to be
+uploaded and an optional thumbnail for that document. To add file upload
+support we need to implement `Fileable`. This only requires one additional
+method.
+
+```go
+type Fileable interface {
+ Chattable
+ files() []RequestFile
+}
+```
+
+First, let's add some fields to store our files in. Most of the standard Configs
+have similar fields for their files.
+
+```diff
+ type DeleteMessageConfig struct {
+ ChannelUsername string
+ ChatID int64
+ MessageID int
++ Delete RequestFileData
++ Thumb RequestFileData
+ }
+```
+
+Adding another method is pretty simple. We'll always add a file named `delete`
+and add the `thumb` file if we have one.
+
+```go
+func (config DeleteMessageConfig) files() []RequestFile {
+ files := []RequestFile{{
+ Name: "delete",
+ Data: config.Delete,
+ }}
+
+ if config.Thumb != nil {
+ files = append(files, RequestFile{
+ Name: "thumb",
+ Data: config.Thumb,
+ })
+ }
+
+ return files
+}
+```
+
+And now our files will upload! It will transparently handle uploads whether File
+is a `FilePath`, `FileURL`, `FileBytes`, `FileReader`, or `FileID`.
+
+### Base Configs
+
+Certain Configs have repeated elements. For example, many of the items sent to a
+chat have `ChatID` or `ChannelUsername` fields, along with `ReplyToMessageID`,
+`ReplyMarkup`, and `DisableNotification`. Instead of implementing all of this
+code for each item, there's a `BaseChat` that handles it for your Config.
+Simply embed it in your struct to get all of those fields.
+
+There's only a few fields required for the `MessageConfig` struct after
+embedding the `BaseChat` struct.
+
+```go
+type MessageConfig struct {
+ BaseChat
+ Text string
+ ParseMode string
+ DisableWebPagePreview bool
+}
+```
+
+It also inherits the `params` method from `BaseChat`. This allows you to call
+it, then you only have to add your new fields.
+
+```go
+func (config MessageConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
+ if err != nil {
+ return params, err
+ }
+
+ params.AddNonEmpty("text", config.Text)
+ // Add your other fields
+
+ return params, nil
+}
+```
+
+Similarly, there's a `BaseFile` struct for adding an associated file and
+`BaseEdit` struct for editing messages.
+
+## Making it Friendly
+
+After we've got a Config type, we'll want to make it more user-friendly. We can
+do this by adding a new helper to `helpers.go`. These are functions that take
+in the required data for the request to succeed and populate a Config.
+
+Telegram only requires two fields to call `deleteMessage`, so this will be fast.
+
+```go
+func NewDeleteMessage(chatID int64, messageID int) DeleteMessageConfig {
+ return DeleteMessageConfig{
+ ChatID: chatID,
+ MessageID: messageID,
+ }
+}
+```
+
+Sometimes it makes sense to add more helpers if there's methods where you have
+to set exactly one field. You can also add helpers that accept a `username`
+string for channels if it's a common operation.
+
+And that's it! You've added a new method.
diff --git a/docs/internals/uploading-files.md b/docs/internals/uploading-files.md
new file mode 100644
index 00000000..45d69bfa
--- /dev/null
+++ b/docs/internals/uploading-files.md
@@ -0,0 +1,87 @@
+# Uploading Files
+
+To make files work as expected, there's a lot going on behind the scenes. Make
+sure to read through the [Files](../getting-started/files.md) section in
+Getting Started first as we'll be building on that information.
+
+This section only talks about file uploading. For non-uploaded files such as
+URLs and file IDs, you just need to pass a string.
+
+## Fields
+
+Let's start by talking about how the library represents files as part of a
+Config.
+
+### Static Fields
+
+Most endpoints use static file fields. For example, `sendPhoto` expects a single
+file named `photo`. All we have to do is set that single field with the correct
+value (either a string or multipart file). Methods like `sendDocument` take two
+file uploads, a `document` and a `thumb`. These are pretty straightforward.
+
+Remembering that the `Fileable` interface only requires one method, let's
+implement it for `DocumentConfig`.
+
+```go
+func (config DocumentConfig) files() []RequestFile {
+ // We can have multiple files, so we'll create an array. We also know that
+ // there always is a document file, so initialize the array with that.
+ files := []RequestFile{{
+ Name: "document",
+ Data: config.File,
+ }}
+
+ // We'll only add a file if we have one.
+ if config.Thumb != nil {
+ files = append(files, RequestFile{
+ Name: "thumb",
+ Data: config.Thumb,
+ })
+ }
+
+ return files
+}
+```
+
+Telegram also supports the `attach://` syntax (discussed more later) for
+thumbnails, but there's no reason to make things more complicated.
+
+### Dynamic Fields
+
+Of course, not everything can be so simple. Methods like `sendMediaGroup`
+can accept many files, and each file can have custom markup. Using a static
+field isn't possible because we need to specify which field is attached to each
+item. Telegram introduced the `attach://` syntax for this.
+
+Let's follow through creating a new media group with string and file uploads.
+
+First, we start by creating some `InputMediaPhoto`.
+
+```go
+photo := tgbotapi.NewInputMediaPhoto(tgbotapi.FilePath("tests/image.jpg"))
+url := tgbotapi.NewInputMediaPhoto(tgbotapi.FileURL("https://i.imgur.com/unQLJIb.jpg"))
+```
+
+This created a new `InputMediaPhoto` struct, with a type of `photo` and the
+media interface that we specified.
+
+We'll now create our media group with the photo and URL.
+
+```go
+mediaGroup := NewMediaGroup(ChatID, []interface{}{
+ photo,
+ url,
+})
+```
+
+A `MediaGroupConfig` stores all of the media in an array of interfaces. We now
+have all of the data we need to upload, but how do we figure out field names for
+uploads? We didn't specify `attach://unique-file` anywhere.
+
+When the library goes to upload the files, it looks at the `params` and `files`
+for the Config. The params are generated by transforming the file into a value
+more suitable for uploading, file IDs and URLs are untouched but uploaded types
+are all changed into `attach://file-%d`. When collecting a list of files to
+upload, it names them the same way. This creates a nearly transparent way of
+handling multiple files in the background without the user having to consider
+what's going on.
diff --git a/go.mod b/go.mod
index 7df46f4b..9e7f65c6 100644
--- a/go.mod
+++ b/go.mod
@@ -1,5 +1,3 @@
-module github.com/go-telegram-bot-api/telegram-bot-api
+module github.com/go-telegram-bot-api/telegram-bot-api/v5
-go 1.12
-
-require github.com/technoweenie/multipartstreamer v1.0.1
+go 1.13
diff --git a/go.sum b/go.sum
index 86606006..e69de29b 100644
--- a/go.sum
+++ b/go.sum
@@ -1,2 +0,0 @@
-github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
-github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
diff --git a/helpers.go b/helpers.go
index e1162246..27ada9e6 100644
--- a/helpers.go
+++ b/helpers.go
@@ -18,30 +18,6 @@ func NewMessage(chatID int64, text string) MessageConfig {
}
}
-// NewDice creates a new DiceConfig.
-//
-// chatID is where to send it
-func NewDice(chatID int64) DiceConfig {
- return DiceConfig{
- BaseChat: BaseChat{
- ChatID: chatID,
- },
- }
-}
-
-// NewDiceWithEmoji creates a new DiceConfig.
-//
-// chatID is where to send it
-// emoji is type of the Dice
-func NewDiceWithEmoji(chatID int64, emoji string) DiceConfig {
- return DiceConfig{
- BaseChat: BaseChat{
- ChatID: chatID,
- },
- Emoji: emoji,
- }
-}
-
// NewDeleteMessage creates a request to delete a message.
func NewDeleteMessage(chatID int64, messageID int) DeleteMessageConfig {
return DeleteMessageConfig{
@@ -76,269 +52,177 @@ func NewForward(chatID int64, fromChatID int64, messageID int) ForwardConfig {
}
}
-// NewPhotoUpload creates a new photo uploader.
+// NewCopyMessage creates a new copy message.
+//
+// chatID is where to send it, fromChatID is the source chat,
+// and messageID is the ID of the original message.
+func NewCopyMessage(chatID int64, fromChatID int64, messageID int) CopyMessageConfig {
+ return CopyMessageConfig{
+ BaseChat: BaseChat{ChatID: chatID},
+ FromChatID: fromChatID,
+ MessageID: messageID,
+ }
+}
+
+// NewPhoto creates a new sendPhoto request.
//
// chatID is where to send it, file is a string path to the file,
// FileReader, or FileBytes.
//
// Note that you must send animated GIFs as a document.
-func NewPhotoUpload(chatID int64, file interface{}) PhotoConfig {
+func NewPhoto(chatID int64, file RequestFileData) PhotoConfig {
return PhotoConfig{
BaseFile: BaseFile{
- BaseChat: BaseChat{ChatID: chatID},
- File: file,
- UseExisting: false,
+ BaseChat: BaseChat{ChatID: chatID},
+ File: file,
},
}
}
-// NewPhotoShare shares an existing photo.
-// You may use this to reshare an existing photo without reuploading it.
+// NewPhotoToChannel creates a new photo uploader to send a photo to a channel.
//
-// chatID is where to send it, fileID is the ID of the file
-// already uploaded.
-func NewPhotoShare(chatID int64, fileID string) PhotoConfig {
+// Note that you must send animated GIFs as a document.
+func NewPhotoToChannel(username string, file RequestFileData) PhotoConfig {
return PhotoConfig{
BaseFile: BaseFile{
- BaseChat: BaseChat{ChatID: chatID},
- FileID: fileID,
- UseExisting: true,
+ BaseChat: BaseChat{
+ ChannelUsername: username,
+ },
+ File: file,
},
}
}
-// NewAudioUpload creates a new audio uploader.
-//
-// chatID is where to send it, file is a string path to the file,
-// FileReader, or FileBytes.
-func NewAudioUpload(chatID int64, file interface{}) AudioConfig {
- return AudioConfig{
- BaseFile: BaseFile{
- BaseChat: BaseChat{ChatID: chatID},
- File: file,
- UseExisting: false,
- },
- }
-}
-
-// NewAudioShare shares an existing audio file.
-// You may use this to reshare an existing audio file without
-// reuploading it.
-//
-// chatID is where to send it, fileID is the ID of the audio
-// already uploaded.
-func NewAudioShare(chatID int64, fileID string) AudioConfig {
+// NewAudio creates a new sendAudio request.
+func NewAudio(chatID int64, file RequestFileData) AudioConfig {
return AudioConfig{
BaseFile: BaseFile{
- BaseChat: BaseChat{ChatID: chatID},
- FileID: fileID,
- UseExisting: true,
+ BaseChat: BaseChat{ChatID: chatID},
+ File: file,
},
}
}
-// NewDocumentUpload creates a new document uploader.
-//
-// chatID is where to send it, file is a string path to the file,
-// FileReader, or FileBytes.
-func NewDocumentUpload(chatID int64, file interface{}) DocumentConfig {
+// NewDocument creates a new sendDocument request.
+func NewDocument(chatID int64, file RequestFileData) DocumentConfig {
return DocumentConfig{
BaseFile: BaseFile{
- BaseChat: BaseChat{ChatID: chatID},
- File: file,
- UseExisting: false,
+ BaseChat: BaseChat{ChatID: chatID},
+ File: file,
},
}
}
-// NewDocumentShare shares an existing document.
-// You may use this to reshare an existing document without
-// reuploading it.
-//
-// chatID is where to send it, fileID is the ID of the document
-// already uploaded.
-func NewDocumentShare(chatID int64, fileID string) DocumentConfig {
- return DocumentConfig{
- BaseFile: BaseFile{
- BaseChat: BaseChat{ChatID: chatID},
- FileID: fileID,
- UseExisting: true,
- },
- }
-}
-
-// NewStickerUpload creates a new sticker uploader.
-//
-// chatID is where to send it, file is a string path to the file,
-// FileReader, or FileBytes.
-func NewStickerUpload(chatID int64, file interface{}) StickerConfig {
+// NewSticker creates a new sendSticker request.
+func NewSticker(chatID int64, file RequestFileData) StickerConfig {
return StickerConfig{
BaseFile: BaseFile{
- BaseChat: BaseChat{ChatID: chatID},
- File: file,
- UseExisting: false,
+ BaseChat: BaseChat{ChatID: chatID},
+ File: file,
},
}
}
-// NewStickerShare shares an existing sticker.
-// You may use this to reshare an existing sticker without
-// reuploading it.
-//
-// chatID is where to send it, fileID is the ID of the sticker
-// already uploaded.
-func NewStickerShare(chatID int64, fileID string) StickerConfig {
- return StickerConfig{
- BaseFile: BaseFile{
- BaseChat: BaseChat{ChatID: chatID},
- FileID: fileID,
- UseExisting: true,
- },
- }
-}
-
-// NewVideoUpload creates a new video uploader.
-//
-// chatID is where to send it, file is a string path to the file,
-// FileReader, or FileBytes.
-func NewVideoUpload(chatID int64, file interface{}) VideoConfig {
+// NewVideo creates a new sendVideo request.
+func NewVideo(chatID int64, file RequestFileData) VideoConfig {
return VideoConfig{
BaseFile: BaseFile{
- BaseChat: BaseChat{ChatID: chatID},
- File: file,
- UseExisting: false,
+ BaseChat: BaseChat{ChatID: chatID},
+ File: file,
},
}
}
-// NewVideoShare shares an existing video.
-// You may use this to reshare an existing video without reuploading it.
-//
-// chatID is where to send it, fileID is the ID of the video
-// already uploaded.
-func NewVideoShare(chatID int64, fileID string) VideoConfig {
- return VideoConfig{
+// NewAnimation creates a new sendAnimation request.
+func NewAnimation(chatID int64, file RequestFileData) AnimationConfig {
+ return AnimationConfig{
BaseFile: BaseFile{
- BaseChat: BaseChat{ChatID: chatID},
- FileID: fileID,
- UseExisting: true,
+ BaseChat: BaseChat{ChatID: chatID},
+ File: file,
},
}
}
-// NewAnimationUpload creates a new animation uploader.
+// NewVideoNote creates a new sendVideoNote request.
//
// chatID is where to send it, file is a string path to the file,
// FileReader, or FileBytes.
-func NewAnimationUpload(chatID int64, file interface{}) AnimationConfig {
- return AnimationConfig{
+func NewVideoNote(chatID int64, length int, file RequestFileData) VideoNoteConfig {
+ return VideoNoteConfig{
BaseFile: BaseFile{
- BaseChat: BaseChat{ChatID: chatID},
- File: file,
- UseExisting: false,
+ BaseChat: BaseChat{ChatID: chatID},
+ File: file,
},
+ Length: length,
}
}
-// NewAnimationShare shares an existing animation.
-// You may use this to reshare an existing animation without reuploading it.
-//
-// chatID is where to send it, fileID is the ID of the animation
-// already uploaded.
-func NewAnimationShare(chatID int64, fileID string) AnimationConfig {
- return AnimationConfig{
+// NewVoice creates a new sendVoice request.
+func NewVoice(chatID int64, file RequestFileData) VoiceConfig {
+ return VoiceConfig{
BaseFile: BaseFile{
- BaseChat: BaseChat{ChatID: chatID},
- FileID: fileID,
- UseExisting: true,
+ BaseChat: BaseChat{ChatID: chatID},
+ File: file,
},
}
}
-// NewVideoNoteUpload creates a new video note uploader.
-//
-// chatID is where to send it, file is a string path to the file,
-// FileReader, or FileBytes.
-func NewVideoNoteUpload(chatID int64, length int, file interface{}) VideoNoteConfig {
- return VideoNoteConfig{
- BaseFile: BaseFile{
- BaseChat: BaseChat{ChatID: chatID},
- File: file,
- UseExisting: false,
- },
- Length: length,
+// NewMediaGroup creates a new media group. Files should be an array of
+// two to ten InputMediaPhoto or InputMediaVideo.
+func NewMediaGroup(chatID int64, files []interface{}) MediaGroupConfig {
+ return MediaGroupConfig{
+ ChatID: chatID,
+ Media: files,
}
}
-// NewVideoNoteShare shares an existing video.
-// You may use this to reshare an existing video without reuploading it.
-//
-// chatID is where to send it, fileID is the ID of the video
-// already uploaded.
-func NewVideoNoteShare(chatID int64, length int, fileID string) VideoNoteConfig {
- return VideoNoteConfig{
- BaseFile: BaseFile{
- BaseChat: BaseChat{ChatID: chatID},
- FileID: fileID,
- UseExisting: true,
+// NewInputMediaPhoto creates a new InputMediaPhoto.
+func NewInputMediaPhoto(media RequestFileData) InputMediaPhoto {
+ return InputMediaPhoto{
+ BaseInputMedia{
+ Type: "photo",
+ Media: media,
},
- Length: length,
}
}
-// NewVoiceUpload creates a new voice uploader.
-//
-// chatID is where to send it, file is a string path to the file,
-// FileReader, or FileBytes.
-func NewVoiceUpload(chatID int64, file interface{}) VoiceConfig {
- return VoiceConfig{
- BaseFile: BaseFile{
- BaseChat: BaseChat{ChatID: chatID},
- File: file,
- UseExisting: false,
+// NewInputMediaVideo creates a new InputMediaVideo.
+func NewInputMediaVideo(media RequestFileData) InputMediaVideo {
+ return InputMediaVideo{
+ BaseInputMedia: BaseInputMedia{
+ Type: "video",
+ Media: media,
},
}
}
-// NewVoiceShare shares an existing voice.
-// You may use this to reshare an existing voice without reuploading it.
-//
-// chatID is where to send it, fileID is the ID of the video
-// already uploaded.
-func NewVoiceShare(chatID int64, fileID string) VoiceConfig {
- return VoiceConfig{
- BaseFile: BaseFile{
- BaseChat: BaseChat{ChatID: chatID},
- FileID: fileID,
- UseExisting: true,
+// NewInputMediaAnimation creates a new InputMediaAnimation.
+func NewInputMediaAnimation(media RequestFileData) InputMediaAnimation {
+ return InputMediaAnimation{
+ BaseInputMedia: BaseInputMedia{
+ Type: "animation",
+ Media: media,
},
}
}
-// NewMediaGroup creates a new media group. Files should be an array of
-// two to ten InputMediaPhoto or InputMediaVideo.
-func NewMediaGroup(chatID int64, files []interface{}) MediaGroupConfig {
- return MediaGroupConfig{
- BaseChat: BaseChat{
- ChatID: chatID,
+// NewInputMediaAudio creates a new InputMediaAudio.
+func NewInputMediaAudio(media RequestFileData) InputMediaAudio {
+ return InputMediaAudio{
+ BaseInputMedia: BaseInputMedia{
+ Type: "audio",
+ Media: media,
},
- InputMedia: files,
}
}
-// NewInputMediaPhoto creates a new InputMediaPhoto.
-func NewInputMediaPhoto(media string) InputMediaPhoto {
- return InputMediaPhoto{
- Type: "photo",
- Media: media,
- }
-}
-
-// NewInputMediaVideo creates a new InputMediaVideo.
-func NewInputMediaVideo(media string) InputMediaVideo {
- return InputMediaVideo{
- Type: "video",
- Media: media,
+// NewInputMediaDocument creates a new InputMediaDocument.
+func NewInputMediaDocument(media RequestFileData) InputMediaDocument {
+ return InputMediaDocument{
+ BaseInputMedia: BaseInputMedia{
+ Type: "document",
+ Media: media,
+ },
}
}
@@ -393,7 +277,7 @@ func NewChatAction(chatID int64, action string) ChatActionConfig {
// NewUserProfilePhotos gets user profile photos.
//
// userID is the ID of the user you wish to get profile photos from.
-func NewUserProfilePhotos(userID int) UserProfilePhotosConfig {
+func NewUserProfilePhotos(userID int64) UserProfilePhotosConfig {
return UserProfilePhotosConfig{
UserID: userID,
Offset: 0,
@@ -416,25 +300,33 @@ func NewUpdate(offset int) UpdateConfig {
// NewWebhook creates a new webhook.
//
// link is the url parsable link you wish to get the updates.
-func NewWebhook(link string) WebhookConfig {
- u, _ := url.Parse(link)
+func NewWebhook(link string) (WebhookConfig, error) {
+ u, err := url.Parse(link)
+
+ if err != nil {
+ return WebhookConfig{}, err
+ }
return WebhookConfig{
URL: u,
- }
+ }, nil
}
// NewWebhookWithCert creates a new webhook with a certificate.
//
// link is the url you wish to get webhooks,
// file contains a string to a file, FileReader, or FileBytes.
-func NewWebhookWithCert(link string, file interface{}) WebhookConfig {
- u, _ := url.Parse(link)
+func NewWebhookWithCert(link string, file RequestFileData) (WebhookConfig, error) {
+ u, err := url.Parse(link)
+
+ if err != nil {
+ return WebhookConfig{}, err
+ }
return WebhookConfig{
URL: u,
Certificate: file,
- }
+ }, nil
}
// NewInlineQueryResultArticle creates a new inline query article.
@@ -502,7 +394,7 @@ func NewInlineQueryResultCachedGIF(id, gifID string) InlineQueryResultCachedGIF
return InlineQueryResultCachedGIF{
Type: "gif",
ID: id,
- GifID: gifID,
+ GIFID: gifID,
}
}
@@ -516,11 +408,11 @@ func NewInlineQueryResultMPEG4GIF(id, url string) InlineQueryResultMPEG4GIF {
}
// NewInlineQueryResultCachedMPEG4GIF create a new inline query with cached MPEG4 GIF.
-func NewInlineQueryResultCachedMPEG4GIF(id, MPEG4GifID string) InlineQueryResultCachedMpeg4Gif {
- return InlineQueryResultCachedMpeg4Gif{
- Type: "mpeg4_gif",
- ID: id,
- MGifID: MPEG4GifID,
+func NewInlineQueryResultCachedMPEG4GIF(id, MPEG4GIFID string) InlineQueryResultCachedMPEG4GIF {
+ return InlineQueryResultCachedMPEG4GIF{
+ Type: "mpeg4_gif",
+ ID: id,
+ MPEG4FileID: MPEG4GIFID,
}
}
@@ -710,17 +602,6 @@ func NewEditMessageReplyMarkup(chatID int64, messageID int, replyMarkup InlineKe
}
}
-// NewHideKeyboard hides the keyboard, with the option for being selective
-// or hiding for everyone.
-func NewHideKeyboard(selective bool) ReplyKeyboardHide {
- log.Println("NewHideKeyboard is deprecated, please use NewRemoveKeyboard")
-
- return ReplyKeyboardHide{
- HideKeyboard: true,
- Selective: selective,
- }
-}
-
// NewRemoveKeyboard hides the keyboard, with the option for being selective
// or hiding for everyone.
func NewRemoveKeyboard(selective bool) ReplyKeyboardRemove {
@@ -792,6 +673,15 @@ func NewInlineKeyboardButtonData(text, data string) InlineKeyboardButton {
}
}
+// NewInlineKeyboardButtonLoginURL creates an inline keyboard button with text
+// which goes to a LoginURL.
+func NewInlineKeyboardButtonLoginURL(text string, loginURL LoginURL) InlineKeyboardButton {
+ return InlineKeyboardButton{
+ Text: text,
+ LoginURL: &loginURL,
+ }
+}
+
// NewInlineKeyboardButtonURL creates an inline keyboard button with text
// which goes to a URL.
func NewInlineKeyboardButtonURL(text, url string) InlineKeyboardButton {
@@ -850,7 +740,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,
@@ -862,33 +752,183 @@ func NewInvoice(chatID int64, title, description, payload, providerToken, startP
Prices: prices}
}
-// NewSetChatPhotoUpload creates a new chat photo uploader.
-//
-// chatID is where to send it, file is a string path to the file,
-// FileReader, or FileBytes.
-//
-// Note that you must send animated GIFs as a document.
-func NewSetChatPhotoUpload(chatID int64, file interface{}) 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 RequestFileData) SetChatPhotoConfig {
return SetChatPhotoConfig{
BaseFile: BaseFile{
- BaseChat: BaseChat{ChatID: chatID},
- File: file,
- UseExisting: false,
+ BaseChat: BaseChat{
+ ChatID: chatID,
+ },
+ File: photo,
},
}
}
-// NewSetChatPhotoShare shares an existing photo.
-// You may use this to reshare an existing photo without reuploading it.
+// NewDeleteChatPhoto allows you to delete the photo for a chat.
+func NewDeleteChatPhoto(chatID int64) DeleteChatPhotoConfig {
+ return DeleteChatPhotoConfig{
+ ChatID: chatID,
+ }
+}
+
+// NewPoll allows you to create a new poll.
+func NewPoll(chatID int64, question string, options ...string) SendPollConfig {
+ return SendPollConfig{
+ BaseChat: BaseChat{
+ ChatID: chatID,
+ },
+ Question: question,
+ Options: options,
+ IsAnonymous: true, // This is Telegram's default.
+ }
+}
+
+// NewStopPoll allows you to stop a poll.
+func NewStopPoll(chatID int64, messageID int) StopPollConfig {
+ return StopPollConfig{
+ BaseEdit{
+ ChatID: chatID,
+ MessageID: messageID,
+ },
+ }
+}
+
+// NewSendDice allows you to send a random dice roll.
//
-// chatID is where to send it, fileID is the ID of the file
-// already uploaded.
-func NewSetChatPhotoShare(chatID int64, fileID string) SetChatPhotoConfig {
- return SetChatPhotoConfig{
- BaseFile: BaseFile{
- BaseChat: BaseChat{ChatID: chatID},
- FileID: fileID,
- UseExisting: true,
+// Deprecated: Use NewDice instead.
+func NewSendDice(chatID int64) DiceConfig {
+ return NewDice(chatID)
+}
+
+// NewDice allows you to send a random dice roll.
+func NewDice(chatID int64) DiceConfig {
+ return DiceConfig{
+ BaseChat: BaseChat{
+ ChatID: chatID,
+ },
+ }
+}
+
+// NewDiceWithEmoji allows you to send a random roll of one of many types.
+//
+// Emoji may be 🎲 (1-6), 🎯 (1-6), or 🏀 (1-5).
+func NewDiceWithEmoji(chatID int64, emoji string) DiceConfig {
+ return DiceConfig{
+ BaseChat: BaseChat{
+ ChatID: chatID,
},
+ Emoji: emoji,
}
}
+
+// NewBotCommandScopeDefault represents the default scope of bot commands.
+func NewBotCommandScopeDefault() BotCommandScope {
+ return BotCommandScope{Type: "default"}
+}
+
+// NewBotCommandScopeAllPrivateChats represents the scope of bot commands,
+// covering all private chats.
+func NewBotCommandScopeAllPrivateChats() BotCommandScope {
+ return BotCommandScope{Type: "all_private_chats"}
+}
+
+// NewBotCommandScopeAllGroupChats represents the scope of bot commands,
+// covering all group and supergroup chats.
+func NewBotCommandScopeAllGroupChats() BotCommandScope {
+ return BotCommandScope{Type: "all_group_chats"}
+}
+
+// NewBotCommandScopeAllChatAdministrators represents the scope of bot commands,
+// covering all group and supergroup chat administrators.
+func NewBotCommandScopeAllChatAdministrators() BotCommandScope {
+ return BotCommandScope{Type: "all_chat_administrators"}
+}
+
+// NewBotCommandScopeChat represents the scope of bot commands, covering a
+// specific chat.
+func NewBotCommandScopeChat(chatID int64) BotCommandScope {
+ return BotCommandScope{
+ Type: "chat",
+ ChatID: chatID,
+ }
+}
+
+// NewBotCommandScopeChatAdministrators represents the scope of bot commands,
+// covering all administrators of a specific group or supergroup chat.
+func NewBotCommandScopeChatAdministrators(chatID int64) BotCommandScope {
+ return BotCommandScope{
+ Type: "chat_administrators",
+ ChatID: chatID,
+ }
+}
+
+// NewBotCommandScopeChatMember represents the scope of bot commands, covering a
+// specific member of a group or supergroup chat.
+func NewBotCommandScopeChatMember(chatID, userID int64) BotCommandScope {
+ return BotCommandScope{
+ Type: "chat_member",
+ ChatID: chatID,
+ UserID: userID,
+ }
+}
+
+// NewGetMyCommandsWithScope allows you to set the registered commands for a
+// given scope.
+func NewGetMyCommandsWithScope(scope BotCommandScope) GetMyCommandsConfig {
+ return GetMyCommandsConfig{Scope: &scope}
+}
+
+// NewGetMyCommandsWithScopeAndLanguage allows you to set the registered
+// commands for a given scope and language code.
+func NewGetMyCommandsWithScopeAndLanguage(scope BotCommandScope, languageCode string) GetMyCommandsConfig {
+ return GetMyCommandsConfig{Scope: &scope, LanguageCode: languageCode}
+}
+
+// NewSetMyCommands allows you to set the registered commands.
+func NewSetMyCommands(commands ...BotCommand) SetMyCommandsConfig {
+ return SetMyCommandsConfig{Commands: commands}
+}
+
+// NewSetMyCommands allows you to set the registered commands for a given scope.
+func NewSetMyCommandsWithScope(scope BotCommandScope, commands ...BotCommand) SetMyCommandsConfig {
+ return SetMyCommandsConfig{Commands: commands, Scope: &scope}
+}
+
+// NewSetMyCommands allows you to set the registered commands for a given scope
+// and language code.
+func NewSetMyCommandsWithScopeAndLanguage(scope BotCommandScope, languageCode string, commands ...BotCommand) SetMyCommandsConfig {
+ return SetMyCommandsConfig{Commands: commands, Scope: &scope, LanguageCode: languageCode}
+}
+
+// NewDeleteMyCommands allows you to delete the registered commands.
+func NewDeleteMyCommands() DeleteMyCommandsConfig {
+ return DeleteMyCommandsConfig{}
+}
+
+// NewDeleteMyCommands allows you to delete the registered commands for a given
+// scope.
+func NewDeleteMyCommandsWithScope(scope BotCommandScope) DeleteMyCommandsConfig {
+ return DeleteMyCommandsConfig{Scope: &scope}
+}
+
+// NewDeleteMyCommands allows you to delete the registered commands for a given
+// scope and language code.
+func NewDeleteMyCommandsWithScopeAndLanguage(scope BotCommandScope, languageCode string) DeleteMyCommandsConfig {
+ return DeleteMyCommandsConfig{Scope: &scope, LanguageCode: languageCode}
+}
diff --git a/helpers_test.go b/helpers_test.go
index ec15a4e7..724f6ac2 100644
--- a/helpers_test.go
+++ b/helpers_test.go
@@ -1,48 +1,71 @@
-package tgbotapi_test
+package tgbotapi
import (
"testing"
-
- tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
)
+func TestNewWebhook(t *testing.T) {
+ result, err := NewWebhook("https://example.com/token")
+
+ if err != nil ||
+ result.URL.String() != "https://example.com/token" ||
+ result.Certificate != interface{}(nil) ||
+ result.MaxConnections != 0 ||
+ len(result.AllowedUpdates) != 0 {
+ t.Fail()
+ }
+}
+
+func TestNewWebhookWithCert(t *testing.T) {
+ exampleFile := FileID("123")
+ result, err := NewWebhookWithCert("https://example.com/token", exampleFile)
+
+ if err != nil ||
+ result.URL.String() != "https://example.com/token" ||
+ result.Certificate != exampleFile ||
+ result.MaxConnections != 0 ||
+ len(result.AllowedUpdates) != 0 {
+ t.Fail()
+ }
+}
+
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", "message")
+ result := NewInlineQueryResultArticleHTML("id", "title", "message")
if result.Type != "article" ||
result.ID != "id" ||
result.Title != "title" ||
- result.InputMessageContent.(tgbotapi.InputTextMessageContent).Text != "message" ||
- result.InputMessageContent.(tgbotapi.InputTextMessageContent).ParseMode != "HTML" {
+ result.InputMessageContent.(InputTextMessageContent).Text != "message" ||
+ 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 +75,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 +85,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 +95,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 +106,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 +116,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 +127,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 +138,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 +150,7 @@ func TestNewInlineQueryResultDocument(t *testing.T) {
}
func TestNewInlineQueryResultLocation(t *testing.T) {
- result := tgbotapi.NewInlineQueryResultLocation("id", "name", 40, 50)
+ result := NewInlineQueryResultLocation("id", "name", 40, 50)
if result.Type != "location" ||
result.ID != "id" ||
@@ -138,8 +161,25 @@ func TestNewInlineQueryResultLocation(t *testing.T) {
}
}
+func TestNewInlineKeyboardButtonLoginURL(t *testing.T) {
+ result := NewInlineKeyboardButtonLoginURL("text", LoginURL{
+ URL: "url",
+ ForwardText: "ForwardText",
+ BotUsername: "username",
+ RequestWriteAccess: false,
+ })
+
+ if result.Text != "text" ||
+ result.LoginURL.URL != "url" ||
+ result.LoginURL.ForwardText != "ForwardText" ||
+ result.LoginURL.BotUsername != "username" ||
+ result.LoginURL.RequestWriteAccess != false {
+ t.Fail()
+ }
+}
+
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 +189,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 +199,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{
+ {
+ {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 ||
@@ -178,7 +218,7 @@ func TestNewEditMessageReplyMarkup(t *testing.T) {
}
func TestNewDice(t *testing.T) {
- dice := tgbotapi.NewDice(42)
+ dice := NewDice(42)
if dice.ChatID != 42 ||
dice.Emoji != "" {
@@ -187,7 +227,7 @@ func TestNewDice(t *testing.T) {
}
func TestNewDiceWithEmoji(t *testing.T) {
- dice := tgbotapi.NewDiceWithEmoji(42, "🏀")
+ dice := NewDiceWithEmoji(42, "🏀")
if dice.ChatID != 42 ||
dice.Emoji != "🏀" {
diff --git a/params.go b/params.go
index 6dce6fb8..289f7e32 100644
--- a/params.go
+++ b/params.go
@@ -2,7 +2,6 @@ package tgbotapi
import (
"encoding/json"
- "net/url"
"reflect"
"strconv"
)
@@ -10,24 +9,6 @@ import (
// Params represents a set of parameters that gets passed to a request.
type Params map[string]string
-func newParams(values url.Values) Params {
- params := Params{}
- for k, v := range values {
- if len(v) > 0 {
- params[k] = v[0]
- }
- }
- return params
-}
-
-func (p Params) toValues() url.Values {
- values := url.Values{}
- for k, v := range p {
- values[k] = []string{v}
- }
- return values
-}
-
// AddNonEmpty adds a value if it not an empty string.
func (p Params) AddNonEmpty(key, value string) {
if value != "" {
diff --git a/params_test.go b/params_test.go
new file mode 100644
index 00000000..75eb0604
--- /dev/null
+++ b/params_test.go
@@ -0,0 +1,93 @@
+package tgbotapi
+
+import (
+ "testing"
+)
+
+func assertLen(t *testing.T, params Params, l int) {
+ actual := len(params)
+ if actual != l {
+ t.Fatalf("Incorrect number of params, expected %d but found %d\n", l, actual)
+ }
+}
+
+func assertEq(t *testing.T, a interface{}, b interface{}) {
+ if a != b {
+ t.Fatalf("Values did not match, a: %v, b: %v\n", a, b)
+ }
+}
+
+func TestAddNonEmpty(t *testing.T) {
+ params := make(Params)
+ params.AddNonEmpty("value", "value")
+ assertLen(t, params, 1)
+ assertEq(t, params["value"], "value")
+ params.AddNonEmpty("test", "")
+ assertLen(t, params, 1)
+ assertEq(t, params["test"], "")
+}
+
+func TestAddNonZero(t *testing.T) {
+ params := make(Params)
+ params.AddNonZero("value", 1)
+ assertLen(t, params, 1)
+ assertEq(t, params["value"], "1")
+ params.AddNonZero("test", 0)
+ assertLen(t, params, 1)
+ assertEq(t, params["test"], "")
+}
+
+func TestAddNonZero64(t *testing.T) {
+ params := make(Params)
+ params.AddNonZero64("value", 1)
+ assertLen(t, params, 1)
+ assertEq(t, params["value"], "1")
+ params.AddNonZero64("test", 0)
+ assertLen(t, params, 1)
+ assertEq(t, params["test"], "")
+}
+
+func TestAddBool(t *testing.T) {
+ params := make(Params)
+ params.AddBool("value", true)
+ assertLen(t, params, 1)
+ assertEq(t, params["value"], "true")
+ params.AddBool("test", false)
+ assertLen(t, params, 1)
+ assertEq(t, params["test"], "")
+}
+
+func TestAddNonZeroFloat(t *testing.T) {
+ params := make(Params)
+ params.AddNonZeroFloat("value", 1)
+ assertLen(t, params, 1)
+ assertEq(t, params["value"], "1.000000")
+ params.AddNonZeroFloat("test", 0)
+ assertLen(t, params, 1)
+ assertEq(t, params["test"], "")
+}
+
+func TestAddInterface(t *testing.T) {
+ params := make(Params)
+ data := struct {
+ Name string `json:"name"`
+ }{
+ Name: "test",
+ }
+ params.AddInterface("value", data)
+ assertLen(t, params, 1)
+ assertEq(t, params["value"], `{"name":"test"}`)
+ params.AddInterface("test", nil)
+ assertLen(t, params, 1)
+ assertEq(t, params["test"], "")
+}
+
+func TestAddFirstValid(t *testing.T) {
+ params := make(Params)
+ params.AddFirstValid("value", 0, "", "test")
+ assertLen(t, params, 1)
+ assertEq(t, params["value"], "test")
+ params.AddFirstValid("value2", 3, "test")
+ assertLen(t, params, 2)
+ assertEq(t, params["value2"], "3")
+}
diff --git a/passport.go b/passport.go
index 5f55006d..8b0177e4 100644
--- a/passport.go
+++ b/passport.go
@@ -61,6 +61,8 @@ type (
// Unique identifier for this file
FileID string `json:"file_id"`
+ FileUniqueID string `json:"file_unique_id"`
+
// File size
FileSize int `json:"file_size"`
diff --git a/types.go b/types.go
index 9da40eb2..c2f0bed0 100644
--- a/types.go
+++ b/types.go
@@ -13,22 +13,29 @@ import (
// stored raw.
type APIResponse struct {
Ok bool `json:"ok"`
- Result json.RawMessage `json:"result"`
- ErrorCode int `json:"error_code"`
- Description string `json:"description"`
- Parameters *ResponseParameters `json:"parameters"`
+ Result json.RawMessage `json:"result,omitempty"`
+ ErrorCode int `json:"error_code,omitempty"`
+ Description string `json:"description,omitempty"`
+ Parameters *ResponseParameters `json:"parameters,omitempty"`
}
-// ResponseParameters are various errors that can be returned in APIResponse.
-type ResponseParameters struct {
- MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional
- RetryAfter int `json:"retry_after"` // optional
+// Error is an error containing extra information returned by the Telegram API.
+type Error struct {
+ Code int
+ Message string
+ ResponseParameters
+}
+
+// Error message string.
+func (e Error) Error() string {
+ return e.Message
}
// Update is an update response, from GetUpdates.
type Update struct {
// UpdateID is the update's unique identifier.
- // Update identifiers start from a certain positive number and increase sequentially.
+ // Update identifiers start from a certain positive number and increase
+ // sequentially.
// This ID becomes especially handy if you're using Webhooks,
// since it allows you to ignore repeated updates or to restore
// the correct update sequence, should they get out of order.
@@ -38,42 +45,75 @@ type Update struct {
// Message new incoming message of any kind — text, photo, sticker, etc.
//
// optional
- Message *Message `json:"message"`
- // EditedMessage
+ Message *Message `json:"message,omitempty"`
+ // EditedMessage new version of a message that is known to the bot and was
+ // edited
//
// optional
- EditedMessage *Message `json:"edited_message"`
- // ChannelPost new version of a message that is known to the bot and was edited
+ EditedMessage *Message `json:"edited_message,omitempty"`
+ // ChannelPost new version of a message that is known to the bot and was
+ // edited
//
// optional
- ChannelPost *Message `json:"channel_post"`
- // EditedChannelPost new incoming channel post of any kind — text, photo, sticker, etc.
+ ChannelPost *Message `json:"channel_post,omitempty"`
+ // EditedChannelPost new incoming channel post of any kind — text, photo,
+ // sticker, etc.
//
// optional
- EditedChannelPost *Message `json:"edited_channel_post"`
+ EditedChannelPost *Message `json:"edited_channel_post,omitempty"`
// InlineQuery new incoming inline query
//
// optional
- InlineQuery *InlineQuery `json:"inline_query"`
+ InlineQuery *InlineQuery `json:"inline_query,omitempty"`
// ChosenInlineResult is the result of an inline query
// that was chosen by a user and sent to their chat partner.
// Please see our documentation on the feedback collecting
// for details on how to enable these updates for your bot.
//
// optional
- ChosenInlineResult *ChosenInlineResult `json:"chosen_inline_result"`
+ ChosenInlineResult *ChosenInlineResult `json:"chosen_inline_result,omitempty"`
// CallbackQuery new incoming callback query
//
// optional
- CallbackQuery *CallbackQuery `json:"callback_query"`
- // ShippingQuery new incoming shipping query. Only for invoices with flexible price
+ CallbackQuery *CallbackQuery `json:"callback_query,omitempty"`
+ // ShippingQuery new incoming shipping query. Only for invoices with
+ // flexible price
+ //
+ // optional
+ ShippingQuery *ShippingQuery `json:"shipping_query,omitempty"`
+ // PreCheckoutQuery new incoming pre-checkout query. Contains full
+ // information about checkout
+ //
+ // optional
+ PreCheckoutQuery *PreCheckoutQuery `json:"pre_checkout_query,omitempty"`
+ // Pool new poll state. Bots receive only updates about stopped polls and
+ // polls, which are sent by the bot
+ //
+ // optional
+ Poll *Poll `json:"poll,omitempty"`
+ // PollAnswer user changed their answer in a non-anonymous poll. Bots
+ // receive new votes only in polls that were sent by the bot itself.
+ //
+ // optional
+ PollAnswer *PollAnswer `json:"poll_answer,omitempty"`
+ // MyChatMember is the bot's chat member status was updated in a chat. For
+ // private chats, this update is received only when the bot is blocked or
+ // unblocked by the user.
+ //
+ // optional
+ MyChatMember *ChatMemberUpdated `json:"my_chat_member"`
+ // ChatMember is a chat member's status was updated in a chat. The bot must
+ // be an administrator in the chat and must explicitly specify "chat_member"
+ // in the list of allowed_updates to receive these updates.
//
// optional
- ShippingQuery *ShippingQuery `json:"shipping_query"`
- // PreCheckoutQuery new incoming pre-checkout query. Contains full information about checkout
+ ChatMember *ChatMemberUpdated `json:"chat_member"`
+ // ChatJoinRequest is a request to join the chat has been sent. The bot must
+ // have the can_invite_users administrator right in the chat to receive
+ // these updates.
//
// optional
- PreCheckoutQuery *PreCheckoutQuery `json:"pre_checkout_query"`
+ ChatJoinRequest *ChatJoinRequest `json:"chat_join_request"`
}
// UpdatesChannel is the channel for getting updates.
@@ -89,26 +129,41 @@ func (ch UpdatesChannel) Clear() {
// User represents a Telegram user or bot.
type User struct {
// ID is a unique identifier for this user or bot
- ID int `json:"id"`
+ ID int64 `json:"id"`
+ // IsBot true, if this user is a bot
+ //
+ // optional
+ IsBot bool `json:"is_bot,omitempty"`
// FirstName user's or bot's first name
FirstName string `json:"first_name"`
// LastName user's or bot's last name
//
// optional
- LastName string `json:"last_name"`
+ LastName string `json:"last_name,omitempty"`
// UserName user's or bot's username
//
// optional
- UserName string `json:"username"`
+ UserName string `json:"username,omitempty"`
// LanguageCode IETF language tag of the user's language
// more info: https://en.wikipedia.org/wiki/IETF_language_tag
//
// optional
- LanguageCode string `json:"language_code"`
- // IsBot true, if this user is a bot
+ LanguageCode string `json:"language_code,omitempty"`
+ // CanJoinGroups is true, if the bot can be invited to groups.
+ // Returned only in getMe.
+ //
+ // optional
+ CanJoinGroups bool `json:"can_join_groups,omitempty"`
+ // CanReadAllGroupMessages is true, if privacy mode is disabled for the bot.
+ // Returned only in getMe.
//
// optional
- IsBot bool `json:"is_bot"`
+ CanReadAllGroupMessages bool `json:"can_read_all_group_messages,omitempty"`
+ // SupportsInlineQueries is true, if the bot supports inline queries.
+ // Returned only in getMe.
+ //
+ // optional
+ SupportsInlineQueries bool `json:"supports_inline_queries,omitempty"`
}
// String displays a simple text version of a user.
@@ -131,25 +186,7 @@ func (u *User) String() string {
return name
}
-// GroupChat is a group chat.
-type GroupChat struct {
- ID int `json:"id"`
- Title string `json:"title"`
-}
-
-// ChatPhoto represents a chat photo.
-type ChatPhoto struct {
- // SmallFileID is a file identifier of small (160x160) chat photo.
- // This file_id can be used only for photo download and
- // only for as long as the photo is not changed.
- SmallFileID string `json:"small_file_id"`
- // BigFileID is a file identifier of big (640x640) chat photo.
- // This file_id can be used only for photo download and
- // only for as long as the photo is not changed.
- BigFileID string `json:"big_file_id"`
-}
-
-// Chat contains information about the place a message was sent.
+// Chat represents a chat.
type Chat struct {
// ID is a unique identifier for this chat
ID int64 `json:"id"`
@@ -158,25 +195,26 @@ type Chat struct {
// Title for supergroups, channels and group chats
//
// optional
- Title string `json:"title"`
+ Title string `json:"title,omitempty"`
// UserName for private chats, supergroups and channels if available
//
// optional
- UserName string `json:"username"`
+ UserName string `json:"username,omitempty"`
// FirstName of the other party in a private chat
//
// optional
- FirstName string `json:"first_name"`
+ FirstName string `json:"first_name,omitempty"`
// LastName of the other party in a private chat
//
// optional
- LastName string `json:"last_name"`
- // AllMembersAreAdmins
- //
- // optional
- AllMembersAreAdmins bool `json:"all_members_are_administrators"`
+ LastName string `json:"last_name,omitempty"`
// Photo is a chat photo
Photo *ChatPhoto `json:"photo"`
+ // Bio is the bio of the other party in a private chat. Returned only in
+ // getChat
+ //
+ // optional
+ Bio string `json:"bio,omitempty"`
// Description for groups, supergroups and channel chats
//
// optional
@@ -187,10 +225,42 @@ type Chat struct {
//
// optional
InviteLink string `json:"invite_link,omitempty"`
- // PinnedMessage Pinned message, for groups, supergroups and channels
+ // PinnedMessage is the pinned message, for groups, supergroups and channels
+ //
+ // optional
+ PinnedMessage *Message `json:"pinned_message,omitempty"`
+ // Permissions is default chat member permissions, for groups and
+ // supergroups. Returned only in getChat.
+ //
+ // optional
+ Permissions *ChatPermissions `json:"permissions,omitempty"`
+ // SlowModeDelay is for supergroups, the minimum allowed delay between
+ // consecutive messages sent by each unpriviledged user. Returned only in
+ // getChat.
+ //
+ // optional
+ SlowModeDelay int `json:"slow_mode_delay,omitempty"`
+ // StickerSetName is for supergroups, name of group sticker set.Returned
+ // only in getChat.
+ //
+ // optional
+ StickerSetName string `json:"sticker_set_name,omitempty"`
+ // CanSetStickerSet is true, if the bot can change the group sticker set.
+ // Returned only in getChat.
+ //
+ // optional
+ CanSetStickerSet bool `json:"can_set_sticker_set,omitempty"`
+ // LinkedChatID is a unique identifier for the linked chat, i.e. the
+ // discussion group identifier for a channel and vice versa; for supergroups
+ // and channel chats.
+ //
+ // optional
+ LinkedChatID int64 `json:"linked_chat_id,omitempty"`
+ // Location is for supergroups, the location to which the supergroup is
+ // connected. Returned only in getChat.
//
// optional
- PinnedMessage *Message `json:"pinned_message"`
+ Location *ChatLocation `json:"location"`
}
// IsPrivate returns if the Chat is a private conversation.
@@ -218,15 +288,21 @@ func (c Chat) ChatConfig() ChatConfig {
return ChatConfig{ChatID: c.ID}
}
-// Message is returned by almost every request, and contains data about
-// almost anything.
+// Message represents a message.
type Message struct {
// MessageID is a unique message identifier inside this chat
MessageID int `json:"message_id"`
// From is a sender, empty for messages sent to channels;
//
// optional
- From *User `json:"from"`
+ From *User `json:"from,omitempty"`
+ // SenderChat is the sender of the message, sent on behalf of a chat. The
+ // channel itself for channel messages. The supergroup itself for messages
+ // from anonymous group administrators. The linked channel for messages
+ // automatically forwarded to the discussion group
+ //
+ // optional
+ SenderChat *Chat `json:"sender_chat,omitempty"`
// Date of the message was sent in Unix time
Date int `json:"date"`
// Chat is the conversation the message belongs to
@@ -234,136 +310,155 @@ type Message struct {
// ForwardFrom for forwarded messages, sender of the original message;
//
// optional
- ForwardFrom *User `json:"forward_from"`
+ ForwardFrom *User `json:"forward_from,omitempty"`
// ForwardFromChat for messages forwarded from channels,
// information about the original channel;
//
// optional
- ForwardFromChat *Chat `json:"forward_from_chat"`
+ ForwardFromChat *Chat `json:"forward_from_chat,omitempty"`
// ForwardFromMessageID for messages forwarded from channels,
// identifier of the original message in the channel;
//
// optional
- ForwardFromMessageID int `json:"forward_from_message_id"`
+ ForwardFromMessageID int `json:"forward_from_message_id,omitempty"`
+ // ForwardSignature for messages forwarded from channels, signature of the
+ // post author if present
+ //
+ // optional
+ ForwardSignature string `json:"forward_signature,omitempty"`
+ // ForwardSenderName is the sender's name for messages forwarded from users
+ // who disallow adding a link to their account in forwarded messages
+ //
+ // optional
+ ForwardSenderName string `json:"forward_sender_name,omitempty"`
// ForwardDate for forwarded messages, date the original message was sent in Unix time;
//
// optional
- ForwardDate int `json:"forward_date"`
+ ForwardDate int `json:"forward_date,omitempty"`
// ReplyToMessage for replies, the original message.
// Note that the Message object in this field will not contain further ReplyToMessage fields
// even if it itself is a reply;
//
// optional
- ReplyToMessage *Message `json:"reply_to_message"`
+ ReplyToMessage *Message `json:"reply_to_message,omitempty"`
// ViaBot through which the message was sent;
//
// optional
- ViaBot *User `json:"via_bot"`
+ ViaBot *User `json:"via_bot,omitempty"`
// EditDate of the message was last edited in Unix time;
//
// optional
- EditDate int `json:"edit_date"`
+ EditDate int `json:"edit_date,omitempty"`
// MediaGroupID is the unique identifier of a media message group this message belongs to;
//
// optional
- MediaGroupID string `json:"media_group_id"`
+ MediaGroupID string `json:"media_group_id,omitempty"`
// AuthorSignature is the signature of the post author for messages in channels;
//
// optional
- AuthorSignature string `json:"author_signature"`
+ AuthorSignature string `json:"author_signature,omitempty"`
// Text is for text messages, the actual UTF-8 text of the message, 0-4096 characters;
//
// optional
- Text string `json:"text"`
+ Text string `json:"text,omitempty"`
// Entities is for text messages, special entities like usernames,
// URLs, bot commands, etc. that appear in the text;
//
// optional
- Entities *[]MessageEntity `json:"entities"`
- // CaptionEntities;
+ Entities []MessageEntity `json:"entities,omitempty"`
+ // Animation message is an animation, information about the animation.
+ // For backward compatibility, when this field is set, the document field will also be set;
//
// optional
- CaptionEntities *[]MessageEntity `json:"caption_entities"`
+ Animation *Animation `json:"animation,omitempty"`
// Audio message is an audio file, information about the file;
//
// optional
- Audio *Audio `json:"audio"`
+ Audio *Audio `json:"audio,omitempty"`
// Document message is a general file, information about the file;
//
// optional
- Document *Document `json:"document"`
- // Animation message is an animation, information about the animation.
- // For backward compatibility, when this field is set, the document field will also be set;
- //
- // optional
- Animation *ChatAnimation `json:"animation"`
- // Game message is a game, information about the game;
- //
- // optional
- Game *Game `json:"game"`
+ Document *Document `json:"document,omitempty"`
// Photo message is a photo, available sizes of the photo;
//
// optional
- Photo *[]PhotoSize `json:"photo"`
+ Photo []PhotoSize `json:"photo,omitempty"`
// Sticker message is a sticker, information about the sticker;
//
// optional
- Sticker *Sticker `json:"sticker"`
+ Sticker *Sticker `json:"sticker,omitempty"`
// Video message is a video, information about the video;
//
// optional
- Video *Video `json:"video"`
+ Video *Video `json:"video,omitempty"`
// VideoNote message is a video note, information about the video message;
//
// optional
- VideoNote *VideoNote `json:"video_note"`
+ VideoNote *VideoNote `json:"video_note,omitempty"`
// Voice message is a voice message, information about the file;
//
// optional
- Voice *Voice `json:"voice"`
+ Voice *Voice `json:"voice,omitempty"`
// Caption for the animation, audio, document, photo, video or voice, 0-1024 characters;
//
// optional
- Caption string `json:"caption"`
+ Caption string `json:"caption,omitempty"`
+ // CaptionEntities;
+ //
+ // optional
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
// Contact message is a shared contact, information about the contact;
//
// optional
- Contact *Contact `json:"contact"`
- // Location message is a shared location, information about the location;
+ Contact *Contact `json:"contact,omitempty"`
+ // Dice is a dice with random value;
+ //
+ // optional
+ Dice *Dice `json:"dice,omitempty"`
+ // Game message is a game, information about the game;
+ //
+ // optional
+ Game *Game `json:"game,omitempty"`
+ // Poll is a native poll, information about the poll;
//
// optional
- Location *Location `json:"location"`
+ Poll *Poll `json:"poll,omitempty"`
// Venue message is a venue, information about the venue.
- // For backward compatibility, when this field is set, the location field will also be set;
+ // For backward compatibility, when this field is set, the location field
+ // will also be set;
//
// optional
- Venue *Venue `json:"venue"`
+ Venue *Venue `json:"venue,omitempty"`
+ // Location message is a shared location, information about the location;
+ //
+ // optional
+ Location *Location `json:"location,omitempty"`
// NewChatMembers that were added to the group or supergroup
// and information about them (the bot itself may be one of these members);
//
// optional
- NewChatMembers *[]User `json:"new_chat_members"`
+ NewChatMembers []User `json:"new_chat_members,omitempty"`
// LeftChatMember is a member was removed from the group,
// information about them (this member may be the bot itself);
//
// optional
- LeftChatMember *User `json:"left_chat_member"`
+ LeftChatMember *User `json:"left_chat_member,omitempty"`
// NewChatTitle is a chat title was changed to this value;
//
// optional
- NewChatTitle string `json:"new_chat_title"`
+ NewChatTitle string `json:"new_chat_title,omitempty"`
// NewChatPhoto is a chat photo was change to this value;
//
// optional
- NewChatPhoto *[]PhotoSize `json:"new_chat_photo"`
+ NewChatPhoto []PhotoSize `json:"new_chat_photo,omitempty"`
// DeleteChatPhoto is a service message: the chat photo was deleted;
//
// optional
- DeleteChatPhoto bool `json:"delete_chat_photo"`
+ DeleteChatPhoto bool `json:"delete_chat_photo,omitempty"`
// GroupChatCreated is a service message: the group has been created;
//
// optional
- GroupChatCreated bool `json:"group_chat_created"`
+ GroupChatCreated bool `json:"group_chat_created,omitempty"`
// SuperGroupChatCreated is a service message: the supergroup has been created.
// This field can't be received in a message coming through updates,
// because bot can't be a member of a supergroup when it is created.
@@ -371,7 +466,7 @@ type Message struct {
// in a directly created supergroup;
//
// optional
- SuperGroupChatCreated bool `json:"supergroup_chat_created"`
+ SuperGroupChatCreated bool `json:"supergroup_chat_created,omitempty"`
// ChannelChatCreated is a service message: the channel has been created.
// This field can't be received in a message coming through updates,
// because bot can't be a member of a channel when it is created.
@@ -379,7 +474,12 @@ type Message struct {
// if someone replies to a very first message in a channel;
//
// optional
- ChannelChatCreated bool `json:"channel_chat_created"`
+ ChannelChatCreated bool `json:"channel_chat_created,omitempty"`
+ // MessageAutoDeleteTimerChanged is a service message: auto-delete timer
+ // settings changed in the chat.
+ //
+ // optional
+ MessageAutoDeleteTimerChanged *MessageAutoDeleteTimerChanged `json:"message_auto_delete_timer_changed"`
// MigrateToChatID is the group has been migrated to a supergroup with the specified identifier.
// This number may be greater than 32 bits and some programming languages
// may have difficulty/silent defects in interpreting it.
@@ -387,7 +487,7 @@ type Message struct {
// or double-precision float type are safe for storing this identifier;
//
// optional
- MigrateToChatID int64 `json:"migrate_to_chat_id"`
+ MigrateToChatID int64 `json:"migrate_to_chat_id,omitempty"`
// MigrateFromChatID is the supergroup has been migrated from a group with the specified identifier.
// This number may be greater than 32 bits and some programming languages
// may have difficulty/silent defects in interpreting it.
@@ -395,26 +495,58 @@ type Message struct {
// or double-precision float type are safe for storing this identifier;
//
// optional
- MigrateFromChatID int64 `json:"migrate_from_chat_id"`
+ MigrateFromChatID int64 `json:"migrate_from_chat_id,omitempty"`
// PinnedMessage is a specified message was pinned.
// Note that the Message object in this field will not contain further ReplyToMessage
// fields even if it is itself a reply;
//
// optional
- PinnedMessage *Message `json:"pinned_message"`
+ PinnedMessage *Message `json:"pinned_message,omitempty"`
// Invoice message is an invoice for a payment;
//
// optional
- Invoice *Invoice `json:"invoice"`
+ Invoice *Invoice `json:"invoice,omitempty"`
// SuccessfulPayment message is a service message about a successful payment,
// information about the payment;
//
// optional
- SuccessfulPayment *SuccessfulPayment `json:"successful_payment"`
+ SuccessfulPayment *SuccessfulPayment `json:"successful_payment,omitempty"`
+ // ConnectedWebsite is Tthe domain name of the website on which the user has
+ // logged in;
+ //
+ // optional
+ ConnectedWebsite string `json:"connected_website,omitempty"`
// PassportData is a Telegram Passport data;
//
// optional
PassportData *PassportData `json:"passport_data,omitempty"`
+ // ProximityAlertTriggered is a service message. A user in the chat
+ // triggered another user's proximity alert while sharing Live Location
+ //
+ // optional
+ ProximityAlertTriggered *ProximityAlertTriggered `json:"proximity_alert_triggered"`
+ // VoiceChatScheduled is a service message: voice chat scheduled.
+ //
+ // optional
+ VoiceChatScheduled *VoiceChatScheduled `json:"voice_chat_scheduled"`
+ // VoiceChatStarted is a service message: voice chat started.
+ //
+ // optional
+ VoiceChatStarted *VoiceChatStarted `json:"voice_chat_started"`
+ // VoiceChatEnded is a service message: voice chat ended.
+ //
+ // optional
+ VoiceChatEnded *VoiceChatEnded `json:"voice_chat_ended"`
+ // VoiceChatParticipantsInvited is a service message: new participants
+ // invited to a voice chat.
+ //
+ // optional
+ VoiceChatParticipantsInvited *VoiceChatParticipantsInvited `json:"voice_chat_participants_invited"`
+ // ReplyMarkup is the Inline keyboard attached to the message.
+ // login_url buttons are represented as ordinary url buttons.
+ //
+ // optional
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
// Time converts the message timestamp into a Time.
@@ -424,11 +556,11 @@ func (m *Message) Time() time.Time {
// IsCommand returns true if message starts with a "bot_command" entity.
func (m *Message) IsCommand() bool {
- if m.Entities == nil || len(*m.Entities) == 0 {
+ if m.Entities == nil || len(m.Entities) == 0 {
return false
}
- entity := (*m.Entities)[0]
+ entity := m.Entities[0]
return entity.Offset == 0 && entity.IsCommand()
}
@@ -458,7 +590,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]
}
@@ -477,7 +609,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
}
@@ -485,7 +618,12 @@ func (m *Message) CommandArguments() string {
return m.Text[entity.Length+1:]
}
-// MessageEntity contains information about data in a Message.
+// MessageID represents a unique message identifier.
+type MessageID struct {
+ MessageID int `json:"message_id"`
+}
+
+// MessageEntity represents one special entity in a text message.
type MessageEntity struct {
// Type of the entity.
// Can be:
@@ -512,11 +650,15 @@ type MessageEntity struct {
// URL for “text_link” only, url that will be opened after user taps on the text
//
// optional
- URL string `json:"url"`
+ URL string `json:"url,omitempty"`
// User for “text_mention” only, the mentioned user
//
// optional
- User *User `json:"user"`
+ User *User `json:"user,omitempty"`
+ // Language for “pre” only, the programming language of the entity text
+ //
+ // optional
+ Language string `json:"language,omitempty"`
}
// ParseURL attempts to parse a URL contained within a MessageEntity.
@@ -543,8 +685,8 @@ func (e MessageEntity) IsCommand() bool {
return e.Type == "bot_command"
}
-// IsUrl returns true if the type of the message entity is "url".
-func (e MessageEntity) IsUrl() bool {
+// IsURL returns true if the type of the message entity is "url".
+func (e MessageEntity) IsURL() bool {
return e.Type == "url"
}
@@ -578,10 +720,15 @@ func (e MessageEntity) IsTextLink() bool {
return e.Type == "text_link"
}
-// PhotoSize contains information about photos.
+// PhotoSize represents one size of a photo or a file / sticker thumbnail.
type PhotoSize struct {
- // FileID identifier for this file, which can be used to download or reuse the file
+ // FileID identifier for this file, which can be used to download or reuse
+ // the file
FileID string `json:"file_id"`
+ // FileUniqueID is the unique identifier for this file, which is supposed to
+ // be the same over time and for different bots. Can't be used to download
+ // or reuse the file.
+ FileUniqueID string `json:"file_unique_id"`
// Width photo width
Width int `json:"width"`
// Height photo height
@@ -589,135 +736,115 @@ type PhotoSize struct {
// FileSize file size
//
// optional
- FileSize int `json:"file_size"`
+ FileSize int `json:"file_size,omitempty"`
}
-// Audio contains information about audio.
-type Audio struct {
- // FileID is an identifier for this file, which can be used to download or reuse the file
+// Animation represents an animation file.
+type Animation struct {
+ // FileID odentifier for this file, which can be used to download or reuse
+ // the file
FileID string `json:"file_id"`
- // Duration of the audio in seconds as defined by sender
+ // FileUniqueID is the unique identifier for this file, which is supposed to
+ // be the same over time and for different bots. Can't be used to download
+ // or reuse the file.
+ FileUniqueID string `json:"file_unique_id"`
+ // Width video width as defined by sender
+ Width int `json:"width"`
+ // Height video height as defined by sender
+ Height int `json:"height"`
+ // Duration of the video in seconds as defined by sender
Duration int `json:"duration"`
- // Performer of the audio as defined by sender or by audio tags
+ // Thumbnail animation thumbnail as defined by sender
//
// optional
- Performer string `json:"performer"`
- // Title of the audio as defined by sender or by audio tags
+ Thumbnail *PhotoSize `json:"thumb,omitempty"`
+ // FileName original animation filename as defined by sender
//
// optional
- Title string `json:"title"`
+ FileName string `json:"file_name,omitempty"`
// MimeType of the file as defined by sender
//
// optional
- MimeType string `json:"mime_type"`
+ MimeType string `json:"mime_type,omitempty"`
// FileSize file size
//
// optional
- FileSize int `json:"file_size"`
+ FileSize int `json:"file_size,omitempty"`
}
-// Document contains information about a document.
-type Document struct {
- // FileID is a identifier for this file, which can be used to download or reuse the file
+// Audio represents an audio file to be treated as music by the Telegram clients.
+type Audio struct {
+ // FileID is an identifier for this file, which can be used to download or
+ // reuse the file
FileID string `json:"file_id"`
- // Thumbnail document thumbnail as defined by sender
- //
- // optional
- Thumbnail *PhotoSize `json:"thumb"`
- // FileName original filename as defined by sender
- //
- // optional
- FileName string `json:"file_name"`
- // MimeType of the file as defined by sender
- //
- // optional
- MimeType string `json:"mime_type"`
- // FileSize file size
+ // FileUniqueID is the unique identifier for this file, which is supposed to
+ // be the same over time and for different bots. Can't be used to download
+ // or reuse the file.
+ FileUniqueID string `json:"file_unique_id"`
+ // Duration of the audio in seconds as defined by sender
+ Duration int `json:"duration"`
+ // Performer of the audio as defined by sender or by audio tags
//
// optional
- FileSize int `json:"file_size"`
-}
-
-// Sticker contains information about a sticker.
-type Sticker struct {
- // FileUniqueID is an unique identifier for this file,
- // which is supposed to be the same over time and for different bots.
- // Can't be used to download or reuse the file.
- FileUniqueID string `json:"file_unique_id"`
- // FileID is an identifier for this file, which can be used to download or reuse the file
- FileID string `json:"file_id"`
- // Width sticker width
- Width int `json:"width"`
- // Height sticker height
- Height int `json:"height"`
- // Thumbnail sticker thumbnail in the .WEBP or .JPG format
+ Performer string `json:"performer,omitempty"`
+ // Title of the audio as defined by sender or by audio tags
//
// optional
- Thumbnail *PhotoSize `json:"thumb"`
- // Emoji associated with the sticker
+ Title string `json:"title,omitempty"`
+ // FileName is the original filename as defined by sender
//
// optional
- Emoji string `json:"emoji"`
- // FileSize
+ FileName string `json:"file_name,omitempty"`
+ // MimeType of the file as defined by sender
//
// optional
- FileSize int `json:"file_size"`
- // SetName of the sticker set to which the sticker belongs
+ MimeType string `json:"mime_type,omitempty"`
+ // FileSize file size
//
// optional
- SetName string `json:"set_name"`
- // IsAnimated true, if the sticker is animated
+ FileSize int `json:"file_size,omitempty"`
+ // Thumbnail is the album cover to which the music file belongs
//
// optional
- IsAnimated bool `json:"is_animated"`
-}
-
-// StickerSet contains information about an sticker set.
-type StickerSet struct {
- // Name sticker set name
- Name string `json:"name"`
- // Title sticker set title
- Title string `json:"title"`
- // IsAnimated true, if the sticker set contains animated stickers
- IsAnimated bool `json:"is_animated"`
- // ContainsMasks true, if the sticker set contains masks
- ContainsMasks bool `json:"contains_masks"`
- // Stickers list of all set stickers
- Stickers []Sticker `json:"stickers"`
+ Thumbnail *PhotoSize `json:"thumb,omitempty"`
}
-// ChatAnimation contains information about an animation.
-type ChatAnimation struct {
- // FileID odentifier for this file, which can be used to download or reuse the file
+// Document represents a general file.
+type Document struct {
+ // FileID is a identifier for this file, which can be used to download or
+ // reuse the file
FileID string `json:"file_id"`
- // Width video width as defined by sender
- Width int `json:"width"`
- // Height video height as defined by sender
- Height int `json:"height"`
- // Duration of the video in seconds as defined by sender
- Duration int `json:"duration"`
- // Thumbnail animation thumbnail as defined by sender
+ // FileUniqueID is the unique identifier for this file, which is supposed to
+ // be the same over time and for different bots. Can't be used to download
+ // or reuse the file.
+ FileUniqueID string `json:"file_unique_id"`
+ // Thumbnail document thumbnail as defined by sender
//
// optional
- Thumbnail *PhotoSize `json:"thumb"`
- // FileName original animation filename as defined by sender
+ Thumbnail *PhotoSize `json:"thumb,omitempty"`
+ // FileName original filename as defined by sender
//
// optional
- FileName string `json:"file_name"`
- // MimeType of the file as defined by sender
+ FileName string `json:"file_name,omitempty"`
+ // MimeType of the file as defined by sender
//
// optional
- MimeType string `json:"mime_type"`
+ MimeType string `json:"mime_type,omitempty"`
// FileSize file size
//
// optional
- FileSize int `json:"file_size"`
+ FileSize int `json:"file_size,omitempty"`
}
-// Video contains information about a video.
+// Video represents a video file.
type Video struct {
- // FileID identifier for this file, which can be used to download or reuse the file
+ // FileID identifier for this file, which can be used to download or reuse
+ // the file
FileID string `json:"file_id"`
+ // FileUniqueID is the unique identifier for this file, which is supposed to
+ // be the same over time and for different bots. Can't be used to download
+ // or reuse the file.
+ FileUniqueID string `json:"file_unique_id"`
// Width video width as defined by sender
Width int `json:"width"`
// Height video height as defined by sender
@@ -727,21 +854,29 @@ type Video struct {
// Thumbnail video thumbnail
//
// optional
- Thumbnail *PhotoSize `json:"thumb"`
+ Thumbnail *PhotoSize `json:"thumb,omitempty"`
+ // FileName is the original filename as defined by sender
+ //
+ // optional
+ FileName string `json:"file_name,omitempty"`
// MimeType of a file as defined by sender
//
// optional
- MimeType string `json:"mime_type"`
+ MimeType string `json:"mime_type,omitempty"`
// FileSize file size
//
// optional
- FileSize int `json:"file_size"`
+ FileSize int `json:"file_size,omitempty"`
}
-// VideoNote contains information about a video.
+// VideoNote object represents a video message.
type VideoNote struct {
// FileID identifier for this file, which can be used to download or reuse the file
FileID string `json:"file_id"`
+ // FileUniqueID is the unique identifier for this file, which is supposed to
+ // be the same over time and for different bots. Can't be used to download
+ // or reuse the file.
+ FileUniqueID string `json:"file_unique_id"`
// Length video width and height (diameter of the video message) as defined by sender
Length int `json:"length"`
// Duration of the video in seconds as defined by sender
@@ -749,30 +884,34 @@ type VideoNote struct {
// Thumbnail video thumbnail
//
// optional
- Thumbnail *PhotoSize `json:"thumb"`
+ Thumbnail *PhotoSize `json:"thumb,omitempty"`
// FileSize file size
//
// optional
- FileSize int `json:"file_size"`
+ FileSize int `json:"file_size,omitempty"`
}
-// Voice contains information about a voice.
+// Voice represents a voice note.
type Voice struct {
// FileID identifier for this file, which can be used to download or reuse the file
FileID string `json:"file_id"`
+ // FileUniqueID is the unique identifier for this file, which is supposed to
+ // be the same over time and for different bots. Can't be used to download
+ // or reuse the file.
+ FileUniqueID string `json:"file_unique_id"`
// Duration of the audio in seconds as defined by sender
Duration int `json:"duration"`
// MimeType of the file as defined by sender
//
// optional
- MimeType string `json:"mime_type"`
+ MimeType string `json:"mime_type,omitempty"`
// FileSize file size
//
// optional
- FileSize int `json:"file_size"`
+ FileSize int `json:"file_size,omitempty"`
}
-// Contact contains information about a contact.
+// Contact represents a phone contact.
//
// Note that LastName and UserID may be empty.
type Contact struct {
@@ -783,37 +922,197 @@ type Contact struct {
// LastName contact's last name
//
// optional
- LastName string `json:"last_name"`
+ LastName string `json:"last_name,omitempty"`
// UserID contact's user identifier in Telegram
//
// optional
- UserID int `json:"user_id"`
+ UserID int64 `json:"user_id,omitempty"`
+ // VCard is additional data about the contact in the form of a vCard.
+ //
+ // optional
+ VCard string `json:"vcard,omitempty"`
+}
+
+// Dice represents an animated emoji that displays a random value.
+type Dice struct {
+ // Emoji on which the dice throw animation is based
+ Emoji string `json:"emoji"`
+ // Value of the dice
+ Value int `json:"value"`
+}
+
+// PollOption contains information about one answer option in a poll.
+type PollOption struct {
+ // Text is the option text, 1-100 characters
+ Text string `json:"text"`
+ // VoterCount is the number of users that voted for this option
+ VoterCount int `json:"voter_count"`
+}
+
+// PollAnswer represents an answer of a user in a non-anonymous poll.
+type PollAnswer struct {
+ // PollID is the unique poll identifier
+ PollID string `json:"poll_id"`
+ // User who changed the answer to the poll
+ User User `json:"user"`
+ // OptionIDs is the 0-based identifiers of poll options chosen by the user.
+ // May be empty if user retracted vote.
+ OptionIDs []int `json:"option_ids"`
+}
+
+// Poll contains information about a poll.
+type Poll struct {
+ // ID is the unique poll identifier
+ ID string `json:"id"`
+ // Question is the poll question, 1-255 characters
+ Question string `json:"question"`
+ // Options is the list of poll options
+ Options []PollOption `json:"options"`
+ // TotalVoterCount is the total numbers of users who voted in the poll
+ TotalVoterCount int `json:"total_voter_count"`
+ // IsClosed is if the poll is closed
+ IsClosed bool `json:"is_closed"`
+ // IsAnonymous is if the poll is anonymous
+ IsAnonymous bool `json:"is_anonymous"`
+ // Type is the poll type, currently can be "regular" or "quiz"
+ Type string `json:"type"`
+ // AllowsMultipleAnswers is true, if the poll allows multiple answers
+ AllowsMultipleAnswers bool `json:"allows_multiple_answers"`
+ // CorrectOptionID is the 0-based identifier of the correct answer option.
+ // Available only for polls in quiz mode, which are closed, or was sent (not
+ // forwarded) by the bot or to the private chat with the bot.
+ //
+ // optional
+ CorrectOptionID int `json:"correct_option_id,omitempty"`
+ // Explanation is text that is shown when a user chooses an incorrect answer
+ // or taps on the lamp icon in a quiz-style poll, 0-200 characters
+ //
+ // optional
+ Explanation string `json:"explanation,omitempty"`
+ // ExplainationEntities are special entities like usernames, URLs, bot
+ // commands, etc. that appear in the explanation
+ //
+ // optional
+ ExplanationEntities []MessageEntity `json:"explanation_entities,omitempty"`
+ // OpenPeriod is the amount of time in seconds the poll will be active
+ // after creation
+ //
+ // optional
+ OpenPeriod int `json:"open_period,omitempty"`
+ // Closedate is the point in time (unix timestamp) when the poll will be
+ // automatically closed
+ //
+ // optional
+ CloseDate int `json:"close_date,omitempty"`
}
-// Location contains information about a place.
+// Location represents a point on the map.
type Location struct {
// Longitude as defined by sender
Longitude float64 `json:"longitude"`
// Latitude as defined by sender
Latitude float64 `json:"latitude"`
+ // HorizontalAccuracy is the radius of uncertainty for the location,
+ // measured in meters; 0-1500
+ //
+ // optional
+ HorizontalAccuracy float64 `json:"horizontal_accuracy,omitempty"`
+ // LivePeriod is time relative to the message sending date, during which the
+ // location can be updated, in seconds. For active live locations only.
+ //
+ // optional
+ LivePeriod int `json:"live_period,omitempty"`
+ // Heading is the direction in which user is moving, in degrees; 1-360. For
+ // active live locations only.
+ //
+ // optional
+ Heading int `json:"heading,omitempty"`
+ // ProximityAlertRadius is the maximum distance for proximity alerts about
+ // approaching another chat member, in meters. For sent live locations only.
+ //
+ // optional
+ ProximityAlertRadius int `json:"proximity_alert_radius,omitempty"`
}
-// Venue contains information about a venue, including its Location.
+// Venue represents a venue.
type Venue struct {
- // Location venue location
+ // Location is the venue location
Location Location `json:"location"`
- // Title name of the venue
+ // Title is the name of the venue
Title string `json:"title"`
// Address of the venue
Address string `json:"address"`
- // FoursquareID foursquare identifier of the venue
+ // FoursquareID is the foursquare identifier of the venue
//
// optional
- FoursquareID string `json:"foursquare_id"`
-}
-
-// UserProfilePhotos contains a set of user profile photos.
-type UserProfilePhotos struct {
+ FoursquareID string `json:"foursquare_id,omitempty"`
+ // FoursquareType is the foursquare type of the venue
+ //
+ // optional
+ FoursquareType string `json:"foursquare_type,omitempty"`
+ // GooglePlaceID is the Google Places identifier of the venue
+ //
+ // optional
+ GooglePlaceID string `json:"google_place_id,omitempty"`
+ // GooglePlaceType is the Google Places type of the venue
+ //
+ // optional
+ GooglePlaceType string `json:"google_place_type,omitempty"`
+}
+
+// ProximityAlertTriggered represents a service message sent when a user in the
+// chat triggers a proximity alert sent by another user.
+type ProximityAlertTriggered struct {
+ // Traveler is the user that triggered the alert
+ Traveler User `json:"traveler"`
+ // Watcher is the user that set the alert
+ Watcher User `json:"watcher"`
+ // Distance is the distance between the users
+ Distance int `json:"distance"`
+}
+
+// MessageAutoDeleteTimerChanged represents a service message about a change in
+// auto-delete timer settings.
+type MessageAutoDeleteTimerChanged struct {
+ // New auto-delete time for messages in the chat.
+ MessageAutoDeleteTime int `json:"message_auto_delete_time"`
+}
+
+// VoiceChatScheduled represents a service message about a voice chat scheduled
+// in the chat.
+type VoiceChatScheduled struct {
+ // Point in time (Unix timestamp) when the voice chat is supposed to be
+ // started by a chat administrator
+ StartDate int `json:"start_date"`
+}
+
+// Time converts the scheduled start date into a Time.
+func (m *VoiceChatScheduled) Time() time.Time {
+ return time.Unix(int64(m.StartDate), 0)
+}
+
+// VoiceChatStarted represents a service message about a voice chat started in
+// the chat.
+type VoiceChatStarted struct{}
+
+// VoiceChatEnded represents a service message about a voice chat ended in the
+// chat.
+type VoiceChatEnded struct {
+ // Voice chat duration; in seconds.
+ Duration int `json:"duration"`
+}
+
+// VoiceChatParticipantsInvited represents a service message about new members
+// invited to a voice chat.
+type VoiceChatParticipantsInvited struct {
+ // New members that were invited to the voice chat.
+ //
+ // optional
+ Users []User `json:"users"`
+}
+
+// UserProfilePhotos contains a set of user profile photos.
+type UserProfilePhotos struct {
// TotalCount total number of profile pictures the target user has
TotalCount int `json:"total_count"`
// Photos requested profile pictures (in up to 4 sizes each)
@@ -822,26 +1121,31 @@ type UserProfilePhotos struct {
// File contains information about a file to download from Telegram.
type File struct {
- // FileID identifier for this file, which can be used to download or reuse the file
+ // FileID identifier for this file, which can be used to download or reuse
+ // the file
FileID string `json:"file_id"`
+ // FileUniqueID is the unique identifier for this file, which is supposed to
+ // be the same over time and for different bots. Can't be used to download
+ // or reuse the file.
+ FileUniqueID string `json:"file_unique_id"`
// FileSize file size, if known
//
// optional
- FileSize int `json:"file_size"`
+ FileSize int `json:"file_size,omitempty"`
// FilePath file path
//
// optional
- FilePath string `json:"file_path"`
+ FilePath string `json:"file_path,omitempty"`
}
// Link returns a full path to the download URL for a File.
//
-// It requires the Bot Token to create the link.
+// It requires the Bot token to create the link.
func (f *File) Link(token string) string {
return fmt.Sprintf(FileEndpoint, token, f.FilePath)
}
-// ReplyKeyboardMarkup allows the Bot to set a custom keyboard.
+// ReplyKeyboardMarkup represents a custom keyboard with reply options.
type ReplyKeyboardMarkup struct {
// Keyboard is an array of button rows, each represented by an Array of KeyboardButton objects
Keyboard [][]KeyboardButton `json:"keyboard"`
@@ -851,7 +1155,7 @@ type ReplyKeyboardMarkup struct {
// is always of the same height as the app's standard keyboard.
//
// optional
- ResizeKeyboard bool `json:"resize_keyboard"`
+ ResizeKeyboard bool `json:"resize_keyboard,omitempty"`
// OneTimeKeyboard requests clients to hide the keyboard as soon as it's been used.
// The keyboard will still be available, but clients will automatically display
// the usual letter-keyboard in the chat – the user can press a special button
@@ -859,7 +1163,12 @@ type ReplyKeyboardMarkup struct {
// Defaults to false.
//
// optional
- OneTimeKeyboard bool `json:"one_time_keyboard"`
+ OneTimeKeyboard bool `json:"one_time_keyboard,omitempty"`
+ // InputFieldPlaceholder is the placeholder to be shown in the input field when
+ // the keyboard is active; 1-64 characters.
+ //
+ // optional
+ InputFieldPlaceholder string `json:"input_field_placeholder,omitempty"`
// Selective use this parameter if you want to show the keyboard to specific users only.
// Targets:
// 1) users that are @mentioned in the text of the Message object;
@@ -870,10 +1179,13 @@ type ReplyKeyboardMarkup struct {
// Other users in the group don't see the keyboard.
//
// optional
- Selective bool `json:"selective"`
+ Selective bool `json:"selective,omitempty"`
}
-// KeyboardButton is a button within a custom keyboard.
+// KeyboardButton represents one button of the reply keyboard. For simple text
+// buttons String can be used instead of this object to specify text of the
+// button. Optional fields request_contact, request_location, and request_poll
+// are mutually exclusive.
type KeyboardButton struct {
// Text of the button. If none of the optional fields are used,
// it will be sent as a message when the button is pressed.
@@ -883,21 +1195,34 @@ type KeyboardButton struct {
// Available in private chats only.
//
// optional
- RequestContact bool `json:"request_contact"`
- // RequestLocation if True, the user's current location will be sent when the button is pressed.
+ RequestContact bool `json:"request_contact,omitempty"`
+ // RequestLocation if True, the user's current location will be sent when
+ // the button is pressed.
// Available in private chats only.
//
// optional
- RequestLocation bool `json:"request_location"`
+ RequestLocation bool `json:"request_location,omitempty"`
+ // RequestPoll if True, the user will be asked to create a poll and send it
+ // to the bot when the button is pressed. Available in private chats only
+ //
+ // optional
+ RequestPoll *KeyboardButtonPollType `json:"request_poll,omitempty"`
}
-// ReplyKeyboardHide allows the Bot to hide a custom keyboard.
-type ReplyKeyboardHide struct {
- HideKeyboard bool `json:"hide_keyboard"`
- Selective bool `json:"selective"` // optional
+// KeyboardButtonPollType represents type of a poll, which is allowed to
+// be created and sent when the corresponding button is pressed.
+type KeyboardButtonPollType struct {
+ // Type is if quiz is passed, the user will be allowed to create only polls
+ // in the quiz mode. If regular is passed, only regular polls will be
+ // allowed. Otherwise, the user will be allowed to create a poll of any type.
+ Type string `json:"type"`
}
-// ReplyKeyboardRemove allows the Bot to hide a custom keyboard.
+// ReplyKeyboardRemove Upon receiving a message with this object, Telegram
+// clients will remove the current custom keyboard and display the default
+// letter-keyboard. By default, custom keyboards are displayed until a new
+// keyboard is sent by a bot. An exception is made for one-time keyboards
+// that are hidden immediately after the user presses a button.
type ReplyKeyboardRemove struct {
// RemoveKeyboard requests clients to remove the custom keyboard
// (user will not be able to summon this keyboard;
@@ -914,17 +1239,19 @@ type ReplyKeyboardRemove struct {
// while still showing the keyboard with poll options to users who haven't voted yet.
//
// optional
- Selective bool `json:"selective"`
+ Selective bool `json:"selective,omitempty"`
}
-// InlineKeyboardMarkup is a custom keyboard presented for an inline bot.
+// InlineKeyboardMarkup represents an inline keyboard that appears right next to
+// the message it belongs to.
type InlineKeyboardMarkup struct {
- // InlineKeyboard array of button rows, each represented by an Array of InlineKeyboardButton objects
+ // InlineKeyboard array of button rows, each represented by an Array of
+ // InlineKeyboardButton objects
InlineKeyboard [][]InlineKeyboardButton `json:"inline_keyboard"`
}
-// InlineKeyboardButton is a button within a custom keyboard for
-// inline query responses.
+// InlineKeyboardButton represents one button of an inline keyboard. You must
+// use exactly one of the optional fields.
//
// Note that some values are references as even an empty string
// will change behavior.
@@ -937,6 +1264,11 @@ type InlineKeyboardButton struct {
//
// optional
URL *string `json:"url,omitempty"`
+ // LoginURL is an HTTP URL used to automatically authorize the user. Can be
+ // used as a replacement for the Telegram Login Widget
+ //
+ // optional
+ LoginURL *LoginURL `json:"login_url,omitempty"`
// CallbackData data to be sent in a callback query to the bot when button is pressed, 1-64 bytes.
//
// optional
@@ -974,54 +1306,158 @@ type InlineKeyboardButton struct {
Pay bool `json:"pay,omitempty"`
}
-// CallbackQuery is data sent when a keyboard button with callback data
-// is clicked.
+// LoginURL represents a parameter of the inline keyboard button used to
+// automatically authorize a user. Serves as a great replacement for the
+// Telegram Login Widget when the user is coming from Telegram. All the user
+// needs to do is tap/click a button and confirm that they want to log in.
+type LoginURL struct {
+ // URL is an HTTP URL to be opened with user authorization data added to the
+ // query string when the button is pressed. If the user refuses to provide
+ // authorization data, the original URL without information about the user
+ // will be opened. The data added is the same as described in Receiving
+ // authorization data.
+ //
+ // NOTE: You must always check the hash of the received data to verify the
+ // authentication and the integrity of the data as described in Checking
+ // authorization.
+ URL string `json:"url"`
+ // ForwardText is the new text of the button in forwarded messages
+ //
+ // optional
+ ForwardText string `json:"forward_text,omitempty"`
+ // BotUsername is the username of a bot, which will be used for user
+ // authorization. See Setting up a bot for more details. If not specified,
+ // the current bot's username will be assumed. The url's domain must be the
+ // same as the domain linked with the bot. See Linking your domain to the
+ // bot for more details.
+ //
+ // optional
+ BotUsername string `json:"bot_username,omitempty"`
+ // RequestWriteAccess if true requests permission for your bot to send
+ // messages to the user
+ //
+ // optional
+ RequestWriteAccess bool `json:"request_write_access,omitempty"`
+}
+
+// CallbackQuery represents an incoming callback query from a callback button in
+// an inline keyboard. If the button that originated the query was attached to a
+// message sent by the bot, the field message will be present. If the button was
+// attached to a message sent via the bot (in inline mode), the field
+// inline_message_id will be present. Exactly one of the fields data or
+// game_short_name will be present.
type CallbackQuery struct {
// ID unique identifier for this query
ID string `json:"id"`
// From sender
From *User `json:"from"`
// Message with the callback button that originated the query.
- // Note that message content and message date will not be available if the message is too old.
+ // Note that message content and message date will not be available if the
+ // message is too old.
//
// optional
- Message *Message `json:"message"`
- // InlineMessageID identifier of the message sent via the bot in inline mode, that originated the query.
+ Message *Message `json:"message,omitempty"`
+ // InlineMessageID identifier of the message sent via the bot in inline
+ // mode, that originated the query.
//
// optional
- //
- InlineMessageID string `json:"inline_message_id"`
- // ChatInstance global identifier, uniquely corresponding to the chat to which
- // the message with the callback button was sent. Useful for high scores in games.
- //
+ InlineMessageID string `json:"inline_message_id,omitempty"`
+ // ChatInstance global identifier, uniquely corresponding to the chat to
+ // which the message with the callback button was sent. Useful for high
+ // scores in games.
ChatInstance string `json:"chat_instance"`
// Data associated with the callback button. Be aware that
// a bad client can send arbitrary data in this field.
//
// optional
- Data string `json:"data"`
+ Data string `json:"data,omitempty"`
// GameShortName short name of a Game to be returned, serves as the unique identifier for the game.
//
// optional
- GameShortName string `json:"game_short_name"`
+ GameShortName string `json:"game_short_name,omitempty"`
}
-// ForceReply allows the Bot to have users directly reply to it without
-// additional interaction.
+// ForceReply when receiving a message with this object, Telegram clients will
+// display a reply interface to the user (act as if the user has selected the
+// bot's message and tapped 'Reply'). This can be extremely useful if you want
+// to create user-friendly step-by-step interfaces without having to sacrifice
+// privacy mode.
type ForceReply struct {
// ForceReply shows reply interface to the user,
// as if they manually selected the bot's message and tapped 'Reply'.
ForceReply bool `json:"force_reply"`
+ // InputFieldPlaceholder is the placeholder to be shown in the input field when
+ // the reply is active; 1-64 characters.
+ //
+ // optional
+ InputFieldPlaceholder string `json:"input_field_placeholder,omitempty"`
// Selective use this parameter if you want to force reply from specific users only.
// Targets:
// 1) users that are @mentioned in the text of the Message object;
// 2) if the bot's message is a reply (has Message.ReplyToMessage not nil), sender of the original message.
//
// optional
- Selective bool `json:"selective"`
+ Selective bool `json:"selective,omitempty"`
+}
+
+// ChatPhoto represents a chat photo.
+type ChatPhoto struct {
+ // SmallFileID is a file identifier of small (160x160) chat photo.
+ // This file_id can be used only for photo download and
+ // only for as long as the photo is not changed.
+ SmallFileID string `json:"small_file_id"`
+ // SmallFileUniqueID is a unique file identifier of small (160x160) chat
+ // photo, which is supposed to be the same over time and for different bots.
+ // Can't be used to download or reuse the file.
+ SmallFileUniqueID string `json:"small_file_unique_id"`
+ // BigFileID is a file identifier of big (640x640) chat photo.
+ // This file_id can be used only for photo download and
+ // only for as long as the photo is not changed.
+ BigFileID string `json:"big_file_id"`
+ // BigFileUniqueID is a file identifier of big (640x640) chat photo, which
+ // is supposed to be the same over time and for different bots. Can't be
+ // used to download or reuse the file.
+ BigFileUniqueID string `json:"big_file_unique_id"`
+}
+
+// ChatInviteLink represents an invite link for a chat.
+type ChatInviteLink struct {
+ // InviteLink is the invite link. If the link was created by another chat
+ // administrator, then the second part of the link will be replaced with “…”.
+ InviteLink string `json:"invite_link"`
+ // Creator of the link.
+ Creator User `json:"creator"`
+ // CreatesJoinRequest is true if users joining the chat via the link need to
+ // be approved by chat administrators.
+ //
+ // optional
+ CreatesJoinRequest bool `json:"creates_join_request"`
+ // IsPrimary is true, if the link is primary.
+ IsPrimary bool `json:"is_primary"`
+ // IsRevoked is true, if the link is revoked.
+ IsRevoked bool `json:"is_revoked"`
+ // Name is the name of the invite link.
+ //
+ // optional
+ Name string `json:"name"`
+ // ExpireDate is the point in time (Unix timestamp) when the link will
+ // expire or has been expired.
+ //
+ // optional
+ ExpireDate int `json:"expire_date"`
+ // MemberLimit is the maximum number of users that can be members of the
+ // chat simultaneously after joining the chat via this invite link; 1-99999.
+ //
+ // optional
+ MemberLimit int `json:"member_limit"`
+ // PendingJoinRequestCount is the number of pending join requests created
+ // using this link.
+ //
+ // optional
+ PendingJoinRequestCount int `json:"pending_join_request_count"`
}
-// ChatMember is information about a member in a chat.
+// ChatMember contains information about one member of a chat.
type ChatMember struct {
// User information about the user
User *User `json:"user"`
@@ -1038,6 +1474,11 @@ type ChatMember struct {
//
// optional
CustomTitle string `json:"custom_title,omitempty"`
+ // IsAnonymous owner and administrators only. True, if the user's presence
+ // in the chat is hidden
+ //
+ // optional
+ IsAnonymous bool `json:"is_anonymous"`
// UntilDate restricted and kicked only.
// Date when restrictions will be lifted for this user;
// unix time.
@@ -1049,12 +1490,15 @@ type ChatMember struct {
//
// optional
CanBeEdited bool `json:"can_be_edited,omitempty"`
- // CanChangeInfo administrators and restricted only.
- // True, if the user is allowed to change the chat title, photo and other settings.
+ // CanManageChat administrators only.
+ // True, if the administrator can access the chat event log, chat
+ // statistics, message statistics in channels, see channel members, see
+ // anonymous administrators in supergoups and ignore slow mode. Implied by
+ // any other administrator privilege.
//
// optional
- CanChangeInfo bool `json:"can_change_info,omitempty"`
- // CanChangeInfo administrators only.
+ CanManageChat bool `json:"can_manage_chat"`
+ // CanPostMessages administrators only.
// True, if the administrator can post in the channel;
// channels only.
//
@@ -1071,20 +1515,16 @@ type ChatMember struct {
//
// optional
CanDeleteMessages bool `json:"can_delete_messages,omitempty"`
- // CanInviteUsers administrators and restricted only.
- // True, if the user is allowed to invite new users to the chat.
+ // CanManageVoiceChats administrators only.
+ // True, if the administrator can manage voice chats.
//
// optional
- CanInviteUsers bool `json:"can_invite_users,omitempty"`
+ CanManageVoiceChats bool `json:"can_manage_voice_chats"`
// CanRestrictMembers administrators only.
// True, if the administrator can restrict, ban or unban chat members.
//
// optional
CanRestrictMembers bool `json:"can_restrict_members,omitempty"`
- // CanPinMessages
- //
- // optional
- CanPinMessages bool `json:"can_pin_messages,omitempty"`
// CanPromoteMembers administrators only.
// True, if the administrator can add new administrators
// with a subset of their own privileges or demote administrators that he has promoted,
@@ -1092,6 +1532,24 @@ type ChatMember struct {
//
// optional
CanPromoteMembers bool `json:"can_promote_members,omitempty"`
+ // CanChangeInfo administrators and restricted only.
+ // True, if the user is allowed to change the chat title, photo and other settings.
+ //
+ // optional
+ CanChangeInfo bool `json:"can_change_info,omitempty"`
+ // CanInviteUsers administrators and restricted only.
+ // True, if the user is allowed to invite new users to the chat.
+ //
+ // optional
+ CanInviteUsers bool `json:"can_invite_users,omitempty"`
+ // CanPinMessages administrators and restricted only.
+ // True, if the user is allowed to pin messages; groups and supergroups only
+ //
+ // optional
+ CanPinMessages bool `json:"can_pin_messages,omitempty"`
+ // IsMember is true, if the user is a member of the chat at the moment of
+ // the request
+ IsMember bool `json:"is_member"`
// CanSendMessages
//
// optional
@@ -1101,6 +1559,11 @@ type ChatMember struct {
//
// optional
CanSendMediaMessages bool `json:"can_send_media_messages,omitempty"`
+ // CanSendPolls restricted only.
+ // True, if the user is allowed to send polls
+ //
+ // optional
+ CanSendPolls bool `json:"can_send_polls,omitempty"`
// CanSendOtherMessages restricted only.
// True, if the user is allowed to send audios, documents,
// photos, videos, video notes and voice notes.
@@ -1120,371 +1583,502 @@ func (chat ChatMember) IsCreator() bool { return chat.Status == "creator" }
// IsAdministrator returns if the ChatMember is a chat administrator.
func (chat ChatMember) IsAdministrator() bool { return chat.Status == "administrator" }
-// IsMember returns if the ChatMember is a current member of the chat.
-func (chat ChatMember) IsMember() bool { return chat.Status == "member" }
-
// HasLeft returns if the ChatMember left the chat.
func (chat ChatMember) HasLeft() bool { return chat.Status == "left" }
// WasKicked returns if the ChatMember was kicked from the chat.
func (chat ChatMember) WasKicked() bool { return chat.Status == "kicked" }
-// Game is a game within Telegram.
-type Game struct {
- // Title of the game
- Title string `json:"title"`
- // Description of the game
- Description string `json:"description"`
- // Photo that will be displayed in the game message in chats.
- Photo []PhotoSize `json:"photo"`
- // Text a brief description of the game or high scores included in the game message.
- // Can be automatically edited to include current high scores for the game
- // when the bot calls setGameScore, or manually edited using editMessageText. 0-4096 characters.
+// ChatMemberUpdated represents changes in the status of a chat member.
+type ChatMemberUpdated struct {
+ // Chat the user belongs to.
+ Chat Chat `json:"chat"`
+ // From is the performer of the action, which resulted in the change.
+ From User `json:"from"`
+ // Date the change was done in Unix time.
+ Date int `json:"date"`
+ // Previous information about the chat member.
+ OldChatMember ChatMember `json:"old_chat_member"`
+ // New information about the chat member.
+ NewChatMember ChatMember `json:"new_chat_member"`
+ // InviteLink is the link which was used by the user to join the chat;
+ // for joining by invite link events only.
//
// optional
- Text string `json:"text"`
- // TextEntities special entities that appear in text, such as usernames, URLs, bot commands, etc.
+ InviteLink *ChatInviteLink `json:"invite_link"`
+}
+
+// ChatJoinRequest represents a join request sent to a chat.
+type ChatJoinRequest struct {
+ // Chat to which the request was sent.
+ Chat Chat `json:"chat"`
+ // User that sent the join request.
+ From User `json:"user"`
+ // Date the request was sent in Unix time.
+ Date int `json:"date"`
+ // Bio of the user.
//
// optional
- TextEntities []MessageEntity `json:"text_entities"`
- // Animation animation that will be displayed in the game message in chats.
- // Upload via BotFather (https://t.me/botfather).
+ Bio string `json:"bio"`
+ // InviteLink is the link that was used by the user to send the join request.
//
// optional
- Animation Animation `json:"animation"`
+ InviteLink *ChatInviteLink `json:"invite_link"`
}
-// Animation is a GIF animation demonstrating the game.
-type Animation struct {
- // FileID identifier for this file, which can be used to download or reuse the file.
- FileID string `json:"file_id"`
- // Thumb animation thumbnail as defined by sender.
+// ChatPermissions describes actions that a non-administrator user is
+// allowed to take in a chat. All fields are optional.
+type ChatPermissions struct {
+ // CanSendMessages is true, if the user is allowed to send text messages,
+ // contacts, locations and venues
//
// optional
- Thumb PhotoSize `json:"thumb"`
- // FileName original animation filename as defined by sender.
+ CanSendMessages bool `json:"can_send_messages,omitempty"`
+ // CanSendMediaMessages is true, if the user is allowed to send audios,
+ // documents, photos, videos, video notes and voice notes, implies
+ // can_send_messages
//
// optional
- FileName string `json:"file_name"`
- // MimeType of the file as defined by sender.
+ CanSendMediaMessages bool `json:"can_send_media_messages,omitempty"`
+ // CanSendPolls is true, if the user is allowed to send polls, implies
+ // can_send_messages
//
// optional
- MimeType string `json:"mime_type"`
- // FileSize ile size
+ CanSendPolls bool `json:"can_send_polls,omitempty"`
+ // CanSendOtherMessages is true, if the user is allowed to send animations,
+ // games, stickers and use inline bots, implies can_send_media_messages
//
// optional
- FileSize int `json:"file_size"`
-}
-
-// GameHighScore is a user's score and position on the leaderboard.
-type GameHighScore struct {
- // Position in high score table for the game
- Position int `json:"position"`
- // User user
- User User `json:"user"`
- // Score score
- Score int `json:"score"`
-}
-
-// CallbackGame is for starting a game in an inline keyboard button.
-type CallbackGame struct{}
-
-// WebhookInfo is information about a currently set webhook.
-type WebhookInfo struct {
- // URL webhook URL, may be empty if webhook is not set up.
- URL string `json:"url"`
- // HasCustomCertificate true, if a custom certificate was provided for webhook certificate checks.
- HasCustomCertificate bool `json:"has_custom_certificate"`
- // PendingUpdateCount number of updates awaiting delivery.
- PendingUpdateCount int `json:"pending_update_count"`
- // LastErrorDate unix time for the most recent error
- // that happened when trying to deliver an update via webhook.
+ CanSendOtherMessages bool `json:"can_send_other_messages,omitempty"`
+ // CanAddWebPagePreviews is true, if the user is allowed to add web page
+ // previews to their messages, implies can_send_media_messages
//
// optional
- LastErrorDate int `json:"last_error_date"`
- // LastErrorMessage error message in human-readable format for the most recent error
- // that happened when trying to deliver an update via webhook.
+ CanAddWebPagePreviews bool `json:"can_add_web_page_previews,omitempty"`
+ // CanChangeInfo is true, if the user is allowed to change the chat title,
+ // photo and other settings. Ignored in public supergroups
//
// optional
- LastErrorMessage string `json:"last_error_message"`
- // MaxConnections maximum allowed number of simultaneous
- // HTTPS connections to the webhook for update delivery.
+ CanChangeInfo bool `json:"can_change_info,omitempty"`
+ // CanInviteUsers is true, if the user is allowed to invite new users to the
+ // chat
+ //
+ // optional
+ CanInviteUsers bool `json:"can_invite_users,omitempty"`
+ // CanPinMessages is true, if the user is allowed to pin messages. Ignored
+ // in public supergroups
//
// optional
- MaxConnections int `json:"max_connections"`
+ CanPinMessages bool `json:"can_pin_messages,omitempty"`
}
-// IsSet returns true if a webhook is currently set.
-func (info WebhookInfo) IsSet() bool {
- return info.URL != ""
+// ChatLocation represents a location to which a chat is connected.
+type ChatLocation struct {
+ // Location is the location to which the supergroup is connected. Can't be a
+ // live location.
+ Location Location `json:"location"`
+ // Address is the location address; 1-64 characters, as defined by the chat
+ // owner
+ Address string `json:"address"`
}
-// InputMediaPhoto contains a photo for displaying as part of a media group.
-type InputMediaPhoto struct {
- // Type of the result, must be photo.
- Type string `json:"type"`
- // Media file to send. Pass a file_id to send a file that
- // exists on the Telegram servers (recommended),
- // pass an HTTP URL for Telegram to get a file from the Internet,
- // or pass “attach://” to upload a new one
- // using multipart/form-data under name.
- Media string `json:"media"`
- // Caption of the photo to be sent, 0-1024 characters after entities parsing.
+// BotCommand represents a bot command.
+type BotCommand struct {
+ // Command text of the command, 1-32 characters.
+ // Can contain only lowercase English letters, digits and underscores.
+ Command string `json:"command"`
+ // Description of the command, 3-256 characters.
+ Description string `json:"description"`
+}
+
+// BotCommandScope represents the scope to which bot commands are applied.
+//
+// It contains the fields for all types of scopes, different types only support
+// specific (or no) fields.
+type BotCommandScope struct {
+ Type string `json:"type"`
+ ChatID int64 `json:"chat_id,omitempty"`
+ UserID int64 `json:"user_id,omitempty"`
+}
+
+// ResponseParameters are various errors that can be returned in APIResponse.
+type ResponseParameters struct {
+ // The group has been migrated to a supergroup with the specified identifier.
//
// optional
- Caption string `json:"caption"`
- // ParseMode mode for parsing entities in the photo caption.
- // See formatting options for more details
- // (https://core.telegram.org/bots/api#formatting-options).
+ MigrateToChatID int64 `json:"migrate_to_chat_id,omitempty"`
+ // In case of exceeding flood control, the number of seconds left to wait
+ // before the request can be repeated.
//
// optional
- ParseMode string `json:"parse_mode"`
+ RetryAfter int `json:"retry_after,omitempty"`
}
-// InputMediaVideo contains a video for displaying as part of a media group.
-type InputMediaVideo struct {
- // Type of the result, must be video.
+// BaseInputMedia is a base type for the InputMedia types.
+type BaseInputMedia struct {
+ // Type of the result.
Type string `json:"type"`
// Media file to send. Pass a file_id to send a file
// that exists on the Telegram servers (recommended),
// pass an HTTP URL for Telegram to get a file from the Internet,
// or pass “attach://” to upload a new one
// using multipart/form-data under name.
- Media string `json:"media"`
+ Media RequestFileData `json:"media"`
// thumb intentionally missing as it is not currently compatible
// Caption of the video to be sent, 0-1024 characters after entities parsing.
//
// optional
- Caption string `json:"caption"`
+ Caption string `json:"caption,omitempty"`
// ParseMode mode for parsing entities in the video caption.
// See formatting options for more details
// (https://core.telegram.org/bots/api#formatting-options).
//
// optional
- ParseMode string `json:"parse_mode"`
+ ParseMode string `json:"parse_mode,omitempty"`
+ // CaptionEntities is a list of special entities that appear in the caption,
+ // which can be specified instead of parse_mode
+ //
+ // optional
+ CaptionEntities []MessageEntity `json:"caption_entities"`
+}
+
+// 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
+ // Thumbnail of the file sent; can be ignored if thumbnail generation for
+ // the file is supported server-side.
+ //
+ // optional
+ Thumb RequestFileData `json:"thumb,omitempty"`
// Width video width
//
// optional
- Width int `json:"width"`
+ Width int `json:"width,omitempty"`
// Height video height
//
// optional
- Height int `json:"height"`
+ Height int `json:"height,omitempty"`
// Duration video duration
//
// optional
- Duration int `json:"duration"`
+ Duration int `json:"duration,omitempty"`
// SupportsStreaming pass True, if the uploaded video is suitable for streaming.
//
// optional
- SupportsStreaming bool `json:"supports_streaming"`
+ SupportsStreaming bool `json:"supports_streaming,omitempty"`
}
-// InlineQuery is a Query from Telegram for an inline request.
-type InlineQuery struct {
- // ID unique identifier for this query
- ID string `json:"id"`
- // From sender
- From *User `json:"from"`
- // Location sender location, only for bots that request user location.
+// InputMediaAnimation is an animation to send as part of a media group.
+type InputMediaAnimation struct {
+ BaseInputMedia
+ // Thumbnail of the file sent; can be ignored if thumbnail generation for
+ // the file is supported server-side.
//
// optional
- Location *Location `json:"location"`
- // Query text of the query (up to 256 characters).
- Query string `json:"query"`
- // Offset of the results to be returned, can be controlled by the bot.
- Offset string `json:"offset"`
-}
-
-// InlineQueryResultArticle is an inline query response article.
-type InlineQueryResultArticle struct {
- // Type of the result, must be article.
- //
- // required
- Type string `json:"type"`
- // ID unique identifier for this result, 1-64 Bytes.
- //
- // required
- ID string `json:"id"`
- // Title of the result
- //
- // required
- Title string `json:"title"`
- // InputMessageContent content of the message to be sent.
- //
- // required
- InputMessageContent interface{} `json:"input_message_content,omitempty"`
- // ReplyMarkup Inline keyboard attached to the message.
+ Thumb RequestFileData `json:"thumb,omitempty"`
+ // Width video width
//
// optional
- ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
- // URL of the result.
+ Width int `json:"width,omitempty"`
+ // Height video height
//
// optional
- URL string `json:"url"`
- // HideURL pass True, if you don't want the URL to be shown in the message.
+ Height int `json:"height,omitempty"`
+ // Duration video duration
//
// optional
- HideURL bool `json:"hide_url"`
- // Description short description of the result.
+ Duration int `json:"duration,omitempty"`
+}
+
+// InputMediaAudio is a audio to send as part of a media group.
+type InputMediaAudio struct {
+ BaseInputMedia
+ // Thumbnail of the file sent; can be ignored if thumbnail generation for
+ // the file is supported server-side.
//
// optional
- Description string `json:"description"`
- // ThumbURL url of the thumbnail for the result
+ Thumb RequestFileData `json:"thumb,omitempty"`
+ // Duration of the audio in seconds
//
// optional
- ThumbURL string `json:"thumb_url"`
- // ThumbWidth thumbnail width
+ Duration int `json:"duration,omitempty"`
+ // Performer of the audio
//
// optional
- ThumbWidth int `json:"thumb_width"`
- // ThumbHeight thumbnail height
+ Performer string `json:"performer,omitempty"`
+ // Title of the audio
//
// optional
- ThumbHeight int `json:"thumb_height"`
+ Title string `json:"title,omitempty"`
}
-// InlineQueryResultPhoto is an inline query response photo.
-type InlineQueryResultPhoto struct {
- // Type of the result, must be article.
+// InputMediaDocument is a general file to send as part of a media group.
+type InputMediaDocument struct {
+ BaseInputMedia
+ // Thumbnail of the file sent; can be ignored if thumbnail generation for
+ // the file is supported server-side.
//
- // required
- Type string `json:"type"`
- // ID unique identifier for this result, 1-64 Bytes.
+ // optional
+ Thumb RequestFileData `json:"thumb,omitempty"`
+ // DisableContentTypeDetection disables automatic server-side content type
+ // detection for files uploaded using multipart/form-data. Always true, if
+ // the document is sent as part of an album
//
- // required
- ID string `json:"id"`
- // URL a valid URL of the photo. Photo must be in jpeg format.
- // Photo size must not exceed 5MB.
- URL string `json:"photo_url"`
- // MimeType
- MimeType string `json:"mime_type"`
- // Width of the photo
+ // optional
+ DisableContentTypeDetection bool `json:"disable_content_type_detection,omitempty"`
+}
+
+// Sticker represents a sticker.
+type Sticker struct {
+ // FileID is an identifier for this file, which can be used to download or
+ // reuse the file
+ FileID string `json:"file_id"`
+ // FileUniqueID is an unique identifier for this file,
+ // which is supposed to be the same over time and for different bots.
+ // Can't be used to download or reuse the file.
+ FileUniqueID string `json:"file_unique_id"`
+ // Width sticker width
+ Width int `json:"width"`
+ // Height sticker height
+ Height int `json:"height"`
+ // IsAnimated true, if the sticker is animated
//
// optional
- Width int `json:"photo_width"`
- // Height of the photo
+ IsAnimated bool `json:"is_animated,omitempty"`
+ // Thumbnail sticker thumbnail in the .WEBP or .JPG format
//
// optional
- Height int `json:"photo_height"`
- // ThumbURL url of the thumbnail for the photo.
+ Thumbnail *PhotoSize `json:"thumb,omitempty"`
+ // Emoji associated with the sticker
//
// optional
- ThumbURL string `json:"thumb_url"`
- // Title for the result
+ Emoji string `json:"emoji,omitempty"`
+ // SetName of the sticker set to which the sticker belongs
//
// optional
- Title string `json:"title"`
- // Description short description of the result
+ SetName string `json:"set_name,omitempty"`
+ // MaskPosition is for mask stickers, the position where the mask should be
+ // placed
//
// optional
- Description string `json:"description"`
- // Caption of the photo to be sent, 0-1024 characters after entities parsing.
+ MaskPosition *MaskPosition `json:"mask_position,omitempty"`
+ // FileSize
//
// optional
- Caption string `json:"caption"`
- // ParseMode mode for parsing entities in the photo caption.
- // See formatting options for more details
- // (https://core.telegram.org/bots/api#formatting-options).
+ FileSize int `json:"file_size,omitempty"`
+}
+
+// StickerSet represents a sticker set.
+type StickerSet struct {
+ // Name sticker set name
+ Name string `json:"name"`
+ // Title sticker set title
+ Title string `json:"title"`
+ // IsAnimated true, if the sticker set contains animated stickers
+ IsAnimated bool `json:"is_animated"`
+ // ContainsMasks true, if the sticker set contains masks
+ ContainsMasks bool `json:"contains_masks"`
+ // Stickers list of all set stickers
+ Stickers []Sticker `json:"stickers"`
+ // Thumb is the sticker set thumbnail in the .WEBP or .TGS format
+ Thumbnail *PhotoSize `json:"thumb"`
+}
+
+// MaskPosition describes the position on faces where a mask should be placed
+// by default.
+type MaskPosition struct {
+ // The part of the face relative to which the mask should be placed.
+ // One of “forehead”, “eyes”, “mouth”, or “chin”.
+ Point string `json:"point"`
+ // Shift by X-axis measured in widths of the mask scaled to the face size,
+ // from left to right. For example, choosing -1.0 will place mask just to
+ // the left of the default mask position.
+ XShift float64 `json:"x_shift"`
+ // Shift by Y-axis measured in heights of the mask scaled to the face size,
+ // from top to bottom. For example, 1.0 will place the mask just below the
+ // default mask position.
+ YShift float64 `json:"y_shift"`
+ // Mask scaling coefficient. For example, 2.0 means double size.
+ Scale float64 `json:"scale"`
+}
+
+// Game represents a game. Use BotFather to create and edit games, their short
+// names will act as unique identifiers.
+type Game struct {
+ // Title of the game
+ Title string `json:"title"`
+ // Description of the game
+ Description string `json:"description"`
+ // Photo that will be displayed in the game message in chats.
+ Photo []PhotoSize `json:"photo"`
+ // Text a brief description of the game or high scores included in the game message.
+ // Can be automatically edited to include current high scores for the game
+ // when the bot calls setGameScore, or manually edited using editMessageText. 0-4096 characters.
//
// optional
- ParseMode string `json:"parse_mode"`
- // ReplyMarkup inline keyboard attached to the message.
+ Text string `json:"text,omitempty"`
+ // TextEntities special entities that appear in text, such as usernames, URLs, bot commands, etc.
//
// optional
- ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
- // InputMessageContent content of the message to be sent instead of the photo.
+ TextEntities []MessageEntity `json:"text_entities,omitempty"`
+ // Animation animation that will be displayed in the game message in chats.
+ // Upload via BotFather (https://t.me/botfather).
//
// optional
- InputMessageContent interface{} `json:"input_message_content,omitempty"`
+ Animation Animation `json:"animation,omitempty"`
}
-// InlineQueryResultCachedPhoto is an inline query response with cached photo.
-type InlineQueryResultCachedPhoto struct {
- // Type of the result, must be photo.
+// GameHighScore is a user's score and position on the leaderboard.
+type GameHighScore struct {
+ // Position in high score table for the game
+ Position int `json:"position"`
+ // User user
+ User User `json:"user"`
+ // Score score
+ Score int `json:"score"`
+}
+
+// CallbackGame is for starting a game in an inline keyboard button.
+type CallbackGame struct{}
+
+// WebhookInfo is information about a currently set webhook.
+type WebhookInfo struct {
+ // URL webhook URL, may be empty if webhook is not set up.
+ URL string `json:"url"`
+ // HasCustomCertificate true, if a custom certificate was provided for webhook certificate checks.
+ HasCustomCertificate bool `json:"has_custom_certificate"`
+ // PendingUpdateCount number of updates awaiting delivery.
+ PendingUpdateCount int `json:"pending_update_count"`
+ // IPAddress is the currently used webhook IP address
//
- // required
- Type string `json:"type"`
- // ID unique identifier for this result, 1-64 bytes.
+ // optional
+ IPAddress string `json:"ip_address,omitempty"`
+ // LastErrorDate unix time for the most recent error
+ // that happened when trying to deliver an update via webhook.
//
- // required
- ID string `json:"id"`
- // PhotoID a valid file identifier of the photo.
+ // optional
+ LastErrorDate int `json:"last_error_date,omitempty"`
+ // LastErrorMessage error message in human-readable format for the most recent error
+ // that happened when trying to deliver an update via webhook.
//
- // required
- PhotoID string `json:"photo_file_id"`
- // Title for the result.
+ // optional
+ LastErrorMessage string `json:"last_error_message,omitempty"`
+ // MaxConnections maximum allowed number of simultaneous
+ // HTTPS connections to the webhook for update delivery.
//
// optional
- Title string `json:"title"`
- // Description short description of the result.
+ MaxConnections int `json:"max_connections,omitempty"`
+ // AllowedUpdates is a list of update types the bot is subscribed to.
+ // Defaults to all update types
//
// optional
- Description string `json:"description"`
- // Caption of the photo to be sent, 0-1024 characters after entities parsing.
+ AllowedUpdates []string `json:"allowed_updates,omitempty"`
+}
+
+// IsSet returns true if a webhook is currently set.
+func (info WebhookInfo) IsSet() bool {
+ return info.URL != ""
+}
+
+// InlineQuery is a Query from Telegram for an inline request.
+type InlineQuery struct {
+ // ID unique identifier for this query
+ ID string `json:"id"`
+ // From sender
+ From *User `json:"from"`
+ // Query text of the query (up to 256 characters).
+ Query string `json:"query"`
+ // Offset of the results to be returned, can be controlled by the bot.
+ Offset string `json:"offset"`
+ // Type of the chat, from which the inline query was sent. Can be either
+ // “sender” for a private chat with the inline query sender, “private”,
+ // “group”, “supergroup”, or “channel”. The chat type should be always known
+ // for requests sent from official clients and most third-party clients,
+ // unless the request was sent from a secret chat
//
// optional
- Caption string `json:"caption"`
- // ParseMode mode for parsing entities in the photo caption.
+ ChatType string `json:"chat_type"`
+ // Location sender location, only for bots that request user location.
+ //
+ // optional
+ Location *Location `json:"location,omitempty"`
+}
+
+// InlineQueryResultCachedAudio is an inline query response with cached audio.
+type InlineQueryResultCachedAudio struct {
+ // Type of the result, must be audio
+ Type string `json:"type"`
+ // ID unique identifier for this result, 1-64 bytes
+ ID string `json:"id"`
+ // AudioID a valid file identifier for the audio file
+ AudioID string `json:"audio_file_id"`
+ // Caption 0-1024 characters after entities parsing
+ //
+ // optional
+ Caption string `json:"caption,omitempty"`
+ // ParseMode mode for parsing entities in the video caption.
// See formatting options for more details
// (https://core.telegram.org/bots/api#formatting-options).
//
// optional
- ParseMode string `json:"parse_mode"`
- // ReplyMarkup inline keyboard attached to the message.
+ ParseMode string `json:"parse_mode,omitempty"`
+ // CaptionEntities is a list of special entities that appear in the caption,
+ // which can be specified instead of parse_mode
+ //
+ // optional
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
+ // ReplyMarkup inline keyboard attached to the message
//
// optional
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
- // InputMessageContent content of the message to be sent instead of the photo.
+ // InputMessageContent content of the message to be sent instead of the audio
//
// optional
InputMessageContent interface{} `json:"input_message_content,omitempty"`
}
-// InlineQueryResultGIF is an inline query response GIF.
-type InlineQueryResultGIF struct {
- // Type of the result, must be gif.
- //
- // required
+// InlineQueryResultCachedDocument is an inline query response with cached document.
+type InlineQueryResultCachedDocument struct {
+ // Type of the result, must be document
Type string `json:"type"`
- // ID unique identifier for this result, 1-64 bytes.
- //
- // required
+ // ID unique identifier for this result, 1-64 bytes
ID string `json:"id"`
- // URL a valid URL for the GIF file. File size must not exceed 1MB.
- //
- // required
- URL string `json:"gif_url"`
- // ThumbURL url of the static (JPEG or GIF) or animated (MPEG4) thumbnail for the result.
- //
- // required
- ThumbURL string `json:"thumb_url"`
- // Width of the GIF
+ // DocumentID a valid file identifier for the file
+ DocumentID string `json:"document_file_id"`
+ // Title for the result
//
// optional
- Width int `json:"gif_width,omitempty"`
- // Height of the GIF
+ Title string `json:"title,omitempty"`
+ // Caption of the document to be sent, 0-1024 characters after entities parsing
//
// optional
- Height int `json:"gif_height,omitempty"`
- // Duration of the GIF
+ Caption string `json:"caption,omitempty"`
+ // Description short description of the result
//
// optional
- Duration int `json:"gif_duration,omitempty"`
- // Title for the result
+ Description string `json:"description,omitempty"`
+ // ParseMode mode for parsing entities in the video caption.
+ // // See formatting options for more details
+ // // (https://core.telegram.org/bots/api#formatting-options).
//
// optional
- Title string `json:"title,omitempty"`
- // Caption of the GIF file to be sent, 0-1024 characters after entities parsing.
+ ParseMode string `json:"parse_mode,omitempty"`
+ // CaptionEntities is a list of special entities that appear in the caption,
+ // which can be specified instead of parse_mode
//
// optional
- Caption string `json:"caption,omitempty"`
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
// ReplyMarkup inline keyboard attached to the message
//
// optional
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
- // InputMessageContent content of the message to be sent instead of the GIF animation.
+ // InputMessageContent content of the message to be sent instead of the file
//
// optional
InputMessageContent interface{} `json:"input_message_content,omitempty"`
@@ -1493,116 +2087,69 @@ type InlineQueryResultGIF struct {
// InlineQueryResultCachedGIF is an inline query response with cached gif.
type InlineQueryResultCachedGIF struct {
// Type of the result, must be gif.
- //
- // required
Type string `json:"type"`
// ID unique identifier for this result, 1-64 bytes.
- //
- // required
ID string `json:"id"`
// GifID a valid file identifier for the GIF file.
- //
- // required
- GifID string `json:"gif_file_id"`
+ GIFID string `json:"gif_file_id"`
// Title for the result
//
// optional
- Title string `json:"title"`
+ Title string `json:"title,omitempty"`
// Caption of the GIF file to be sent, 0-1024 characters after entities parsing.
//
// optional
- Caption string `json:"caption"`
+ Caption string `json:"caption,omitempty"`
// ParseMode mode for parsing entities in the caption.
// See formatting options for more details
// (https://core.telegram.org/bots/api#formatting-options).
//
// optional
- ParseMode string `json:"parse_mode"`
- // ReplyMarkup inline keyboard attached to the message.
+ ParseMode string `json:"parse_mode,omitempty"`
+ // CaptionEntities is a list of special entities that appear in the caption,
+ // which can be specified instead of parse_mode
//
// optional
- ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
- // InputMessageContent content of the message to be sent instead of the GIF animation.
- //
- // optional
- InputMessageContent interface{} `json:"input_message_content,omitempty"`
-}
-
-// InlineQueryResultMPEG4GIF is an inline query response MPEG4 GIF.
-type InlineQueryResultMPEG4GIF struct {
- // Type of the result, must be mpeg4_gif
- //
- // required
- Type string `json:"type"`
- // ID unique identifier for this result, 1-64 bytes
- //
- // required
- ID string `json:"id"`
- // URL a valid URL for the MP4 file. File size must not exceed 1MB
- //
- // required
- URL string `json:"mpeg4_url"`
- // Width video width
- //
- // optional
- Width int `json:"mpeg4_width"`
- // Height vVideo height
- //
- // optional
- Height int `json:"mpeg4_height"`
- // Duration video duration
- //
- // optional
- Duration int `json:"mpeg4_duration"`
- // ThumbURL url of the static (JPEG or GIF) or animated (MPEG4) thumbnail for the result.
- ThumbURL string `json:"thumb_url"`
- // Title for the result
- //
- // optional
- Title string `json:"title"`
- // Caption of the MPEG-4 file to be sent, 0-1024 characters after entities parsing.
- //
- // optional
- Caption string `json:"caption"`
- // ReplyMarkup inline keyboard attached to the message
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
+ // ReplyMarkup inline keyboard attached to the message.
//
// optional
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
- // InputMessageContent content of the message to be sent instead of the video animation
+ // InputMessageContent content of the message to be sent instead of the GIF animation.
//
// optional
InputMessageContent interface{} `json:"input_message_content,omitempty"`
}
-// InlineQueryResultCachedMpeg4Gif is an inline query response with cached
+// InlineQueryResultCachedMPEG4GIF is an inline query response with cached
// H.264/MPEG-4 AVC video without sound gif.
-type InlineQueryResultCachedMpeg4Gif struct {
+type InlineQueryResultCachedMPEG4GIF struct {
// Type of the result, must be mpeg4_gif
- //
- // required
Type string `json:"type"`
// ID unique identifier for this result, 1-64 bytes
- //
- // required
ID string `json:"id"`
- // MGifID a valid file identifier for the MP4 file
- //
- // required
- MGifID string `json:"mpeg4_file_id"`
+ // MPEG4FileID a valid file identifier for the MP4 file
+ MPEG4FileID string `json:"mpeg4_file_id"`
// Title for the result
//
// optional
- Title string `json:"title"`
+ Title string `json:"title,omitempty"`
// Caption of the MPEG-4 file to be sent, 0-1024 characters after entities parsing.
//
// optional
- Caption string `json:"caption"`
+ Caption string `json:"caption,omitempty"`
// ParseMode mode for parsing entities in the caption.
// See formatting options for more details
// (https://core.telegram.org/bots/api#formatting-options).
//
// optional
- ParseMode string `json:"parse_mode"`
+ ParseMode string `json:"parse_mode,omitempty"`
+ // ParseMode mode for parsing entities in the video caption.
+ // See formatting options for more details
+ // (https://core.telegram.org/bots/api#formatting-options).
+ //
+ // optional
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
// ReplyMarkup inline keyboard attached to the message.
//
// optional
@@ -1613,59 +2160,62 @@ type InlineQueryResultCachedMpeg4Gif struct {
InputMessageContent interface{} `json:"input_message_content,omitempty"`
}
-// InlineQueryResultVideo is an inline query response video.
-type InlineQueryResultVideo struct {
- // Type of the result, must be video
- //
- // required
+// InlineQueryResultCachedPhoto is an inline query response with cached photo.
+type InlineQueryResultCachedPhoto struct {
+ // Type of the result, must be photo.
Type string `json:"type"`
- // ID unique identifier for this result, 1-64 bytes
- //
- // required
+ // ID unique identifier for this result, 1-64 bytes.
ID string `json:"id"`
- // URL a valid url for the embedded video player or video file
- //
- // required
- URL string `json:"video_url"`
- // MimeType of the content of video url, “text/html” or “video/mp4”
- //
- // required
- MimeType string `json:"mime_type"`
+ // PhotoID a valid file identifier of the photo.
+ PhotoID string `json:"photo_file_id"`
+ // Title for the result.
//
- // ThumbURL url of the thumbnail (jpeg only) for the video
// optional
- ThumbURL string `json:"thumb_url"`
- // Title for the result
+ Title string `json:"title,omitempty"`
+ // Description short description of the result.
//
- // required
- Title string `json:"title"`
- // Caption of the video to be sent, 0-1024 characters after entities parsing
+ // optional
+ Description string `json:"description,omitempty"`
+ // Caption of the photo to be sent, 0-1024 characters after entities parsing.
//
// optional
- Caption string `json:"caption"`
- // Width video width
+ Caption string `json:"caption,omitempty"`
+ // ParseMode mode for parsing entities in the photo caption.
+ // See formatting options for more details
+ // (https://core.telegram.org/bots/api#formatting-options).
//
// optional
- Width int `json:"video_width"`
- // Height video height
+ ParseMode string `json:"parse_mode,omitempty"`
+ // CaptionEntities is a list of special entities that appear in the caption,
+ // which can be specified instead of parse_mode
//
// optional
- Height int `json:"video_height"`
- // Duration video duration in seconds
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
+ // ReplyMarkup inline keyboard attached to the message.
//
// optional
- Duration int `json:"video_duration"`
- // Description short description of the result
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+ // InputMessageContent content of the message to be sent instead of the photo.
//
// optional
- Description string `json:"description"`
+ InputMessageContent interface{} `json:"input_message_content,omitempty"`
+}
+
+// InlineQueryResultCachedSticker is an inline query response with cached sticker.
+type InlineQueryResultCachedSticker struct {
+ // Type of the result, must be sticker
+ Type string `json:"type"`
+ // ID unique identifier for this result, 1-64 bytes
+ ID string `json:"id"`
+ // StickerID a valid file identifier of the sticker
+ StickerID string `json:"sticker_file_id"`
+ // Title is a title
+ Title string `json:"title"`
// ReplyMarkup inline keyboard attached to the message
//
// optional
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
- // InputMessageContent content of the message to be sent instead of the video.
- // This field is required if InlineQueryResultVideo is used to send
- // an HTML-page as a result (e.g., a YouTube video).
+ // InputMessageContent content of the message to be sent instead of the sticker
//
// optional
InputMessageContent interface{} `json:"input_message_content,omitempty"`
@@ -1674,35 +2224,32 @@ type InlineQueryResultVideo struct {
// InlineQueryResultCachedVideo is an inline query response with cached video.
type InlineQueryResultCachedVideo struct {
// Type of the result, must be video
- //
- // required
Type string `json:"type"`
// ID unique identifier for this result, 1-64 bytes
- //
- // required
ID string `json:"id"`
// VideoID a valid file identifier for the video file
- //
- // required
VideoID string `json:"video_file_id"`
// Title for the result
- //
- // required
Title string `json:"title"`
// Description short description of the result
//
// optional
- Description string `json:"description"`
+ Description string `json:"description,omitempty"`
// Caption of the video to be sent, 0-1024 characters after entities parsing
//
// optional
- Caption string `json:"caption"`
+ Caption string `json:"caption,omitempty"`
// ParseMode mode for parsing entities in the video caption.
// See formatting options for more details
// (https://core.telegram.org/bots/api#formatting-options).
//
// optional
- ParseMode string `json:"parse_mode"`
+ ParseMode string `json:"parse_mode,omitempty"`
+ // CaptionEntities is a list of special entities that appear in the caption,
+ // which can be specified instead of parse_mode
+ //
+ // optional
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
// ReplyMarkup inline keyboard attached to the message
//
// optional
@@ -1713,102 +2260,114 @@ type InlineQueryResultCachedVideo struct {
InputMessageContent interface{} `json:"input_message_content,omitempty"`
}
-// InlineQueryResultCachedSticker is an inline query response with cached sticker.
-type InlineQueryResultCachedSticker struct {
- // Type of the result, must be sticker
- //
- // required
+// InlineQueryResultCachedVoice is an inline query response with cached voice.
+type InlineQueryResultCachedVoice struct {
+ // Type of the result, must be voice
Type string `json:"type"`
// ID unique identifier for this result, 1-64 bytes
- //
- // required
ID string `json:"id"`
- // StickerID a valid file identifier of the sticker
- //
- // required
- StickerID string `json:"sticker_file_id"`
- // Title is a title
+ // VoiceID a valid file identifier for the voice message
+ VoiceID string `json:"voice_file_id"`
+ // Title voice message title
Title string `json:"title"`
+ // Caption 0-1024 characters after entities parsing
+ //
+ // optional
+ Caption string `json:"caption,omitempty"`
// ParseMode mode for parsing entities in the video caption.
// See formatting options for more details
// (https://core.telegram.org/bots/api#formatting-options).
//
// optional
- ParseMode string `json:"parse_mode"`
+ ParseMode string `json:"parse_mode,omitempty"`
+ // CaptionEntities is a list of special entities that appear in the caption,
+ // which can be specified instead of parse_mode
+ //
+ // optional
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
// ReplyMarkup inline keyboard attached to the message
//
// optional
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
- // InputMessageContent content of the message to be sent instead of the sticker
+ // InputMessageContent content of the message to be sent instead of the voice message
//
// optional
InputMessageContent interface{} `json:"input_message_content,omitempty"`
}
-// InlineQueryResultAudio is an inline query response audio.
-type InlineQueryResultAudio struct {
- // Type of the result, must be audio
- //
- // required
+// InlineQueryResultArticle represents a link to an article or web page.
+type InlineQueryResultArticle struct {
+ // Type of the result, must be article.
Type string `json:"type"`
- // ID unique identifier for this result, 1-64 bytes
- //
- // required
+ // ID unique identifier for this result, 1-64 Bytes.
ID string `json:"id"`
- // URL a valid url for the audio file
+ // Title of the result
+ Title string `json:"title"`
+ // InputMessageContent content of the message to be sent.
+ InputMessageContent interface{} `json:"input_message_content,omitempty"`
+ // ReplyMarkup Inline keyboard attached to the message.
//
- // required
- URL string `json:"audio_url"`
- // Title is a title
+ // optional
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+ // URL of the result.
//
- // required
- Title string `json:"title"`
- // Caption 0-1024 characters after entities parsing
+ // optional
+ URL string `json:"url,omitempty"`
+ // HideURL pass True, if you don't want the URL to be shown in the message.
//
// optional
- Caption string `json:"caption"`
- // Performer is a performer
+ HideURL bool `json:"hide_url,omitempty"`
+ // Description short description of the result.
//
// optional
- Performer string `json:"performer"`
- // Duration audio duration in seconds
+ Description string `json:"description,omitempty"`
+ // ThumbURL url of the thumbnail for the result
//
// optional
- Duration int `json:"audio_duration"`
- // ReplyMarkup inline keyboard attached to the message
+ ThumbURL string `json:"thumb_url,omitempty"`
+ // ThumbWidth thumbnail width
//
// optional
- ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
- // InputMessageContent content of the message to be sent instead of the audio
+ ThumbWidth int `json:"thumb_width,omitempty"`
+ // ThumbHeight thumbnail height
//
// optional
- InputMessageContent interface{} `json:"input_message_content,omitempty"`
+ ThumbHeight int `json:"thumb_height,omitempty"`
}
-// InlineQueryResultCachedAudio is an inline query response with cached audio.
-type InlineQueryResultCachedAudio struct {
+// InlineQueryResultAudio is an inline query response audio.
+type InlineQueryResultAudio struct {
// Type of the result, must be audio
- //
- // required
Type string `json:"type"`
// ID unique identifier for this result, 1-64 bytes
- //
- // required
ID string `json:"id"`
- // AudioID a valid file identifier for the audio file
- //
- // required
- AudioID string `json:"audio_file_id"`
+ // URL a valid url for the audio file
+ URL string `json:"audio_url"`
+ // Title is a title
+ Title string `json:"title"`
// Caption 0-1024 characters after entities parsing
//
// optional
- Caption string `json:"caption"`
+ Caption string `json:"caption,omitempty"`
// ParseMode mode for parsing entities in the video caption.
// See formatting options for more details
// (https://core.telegram.org/bots/api#formatting-options).
//
// optional
- ParseMode string `json:"parse_mode"`
+ ParseMode string `json:"parse_mode,omitempty"`
+ // CaptionEntities is a list of special entities that appear in the caption,
+ // which can be specified instead of parse_mode
+ //
+ // optional
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
+ // Performer is a performer
+ //
+ // optional
+ Performer string `json:"performer,omitempty"`
+ // Duration audio duration in seconds
+ //
+ // optional
+ Duration int `json:"audio_duration,omitempty"`
// ReplyMarkup inline keyboard attached to the message
//
// optional
@@ -1819,253 +2378,321 @@ type InlineQueryResultCachedAudio struct {
InputMessageContent interface{} `json:"input_message_content,omitempty"`
}
-// InlineQueryResultVoice is an inline query response voice.
-type InlineQueryResultVoice struct {
- // Type of the result, must be voice
- //
- // required
+// InlineQueryResultContact is an inline query response contact.
+type InlineQueryResultContact struct {
+ Type string `json:"type"` // required
+ ID string `json:"id"` // required
+ PhoneNumber string `json:"phone_number"` // required
+ FirstName string `json:"first_name"` // required
+ LastName string `json:"last_name"`
+ VCard string `json:"vcard"`
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+ InputMessageContent interface{} `json:"input_message_content,omitempty"`
+ ThumbURL string `json:"thumb_url"`
+ ThumbWidth int `json:"thumb_width"`
+ ThumbHeight int `json:"thumb_height"`
+}
+
+// InlineQueryResultGame is an inline query response game.
+type InlineQueryResultGame struct {
+ // Type of the result, must be game
Type string `json:"type"`
// ID unique identifier for this result, 1-64 bytes
- //
- // required
ID string `json:"id"`
- // URL a valid URL for the voice recording
- //
- // required
- URL string `json:"voice_url"`
- // Title recording title
+ // GameShortName short name of the game
+ GameShortName string `json:"game_short_name"`
+ // ReplyMarkup inline keyboard attached to the message
//
- // required
+ // optional
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+}
+
+// InlineQueryResultDocument is an inline query response document.
+type InlineQueryResultDocument struct {
+ // Type of the result, must be document
+ Type string `json:"type"`
+ // ID unique identifier for this result, 1-64 bytes
+ ID string `json:"id"`
+ // Title for the result
Title string `json:"title"`
- // Caption 0-1024 characters after entities parsing
+ // Caption of the document to be sent, 0-1024 characters after entities parsing
//
// optional
- Caption string `json:"caption"`
- // Duration recording duration in seconds
+ Caption string `json:"caption,omitempty"`
+ // URL a valid url for the file
+ URL string `json:"document_url"`
+ // MimeType of the content of the file, either “application/pdf” or “application/zip”
+ MimeType string `json:"mime_type"`
+ // Description short description of the result
//
// optional
- Duration int `json:"voice_duration"`
- // ReplyMarkup inline keyboard attached to the message
+ Description string `json:"description,omitempty"`
+ // ReplyMarkup nline keyboard attached to the message
//
// optional
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
- // InputMessageContent content of the message to be sent instead of the voice recording
+ // InputMessageContent content of the message to be sent instead of the file
//
// optional
InputMessageContent interface{} `json:"input_message_content,omitempty"`
+ // ThumbURL url of the thumbnail (jpeg only) for the file
+ //
+ // optional
+ ThumbURL string `json:"thumb_url,omitempty"`
+ // ThumbWidth thumbnail width
+ //
+ // optional
+ ThumbWidth int `json:"thumb_width,omitempty"`
+ // ThumbHeight thumbnail height
+ //
+ // optional
+ ThumbHeight int `json:"thumb_height,omitempty"`
}
-// InlineQueryResultCachedVoice is an inline query response with cached voice.
-type InlineQueryResultCachedVoice struct {
- // Type of the result, must be voice
- //
- // required
+// InlineQueryResultGIF is an inline query response GIF.
+type InlineQueryResultGIF struct {
+ // Type of the result, must be gif.
Type string `json:"type"`
- // ID unique identifier for this result, 1-64 bytes
- //
- // required
+ // ID unique identifier for this result, 1-64 bytes.
ID string `json:"id"`
- // VoiceID a valid file identifier for the voice message
+ // URL a valid URL for the GIF file. File size must not exceed 1MB.
+ URL string `json:"gif_url"`
+ // ThumbURL url of the static (JPEG or GIF) or animated (MPEG4) thumbnail for the result.
+ ThumbURL string `json:"thumb_url"`
+ // Width of the GIF
//
- // required
- VoiceID string `json:"voice_file_id"`
- // Title voice message title
+ // optional
+ Width int `json:"gif_width,omitempty"`
+ // Height of the GIF
//
- // required
- Title string `json:"title"`
- // Caption 0-1024 characters after entities parsing
+ // optional
+ Height int `json:"gif_height,omitempty"`
+ // Duration of the GIF
+ //
+ // optional
+ Duration int `json:"gif_duration,omitempty"`
+ // Title for the result
+ //
+ // optional
+ Title string `json:"title,omitempty"`
+ // Caption of the GIF file to be sent, 0-1024 characters after entities parsing.
//
// optional
- Caption string `json:"caption"`
+ Caption string `json:"caption,omitempty"`
// ParseMode mode for parsing entities in the video caption.
// See formatting options for more details
// (https://core.telegram.org/bots/api#formatting-options).
//
// optional
- ParseMode string `json:"parse_mode"`
+ ParseMode string `json:"parse_mode,omitempty"`
+ // CaptionEntities is a list of special entities that appear in the caption,
+ // which can be specified instead of parse_mode
+ //
+ // optional
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
// ReplyMarkup inline keyboard attached to the message
//
// optional
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
- // InputMessageContent content of the message to be sent instead of the voice message
+ // InputMessageContent content of the message to be sent instead of the GIF animation.
//
// optional
InputMessageContent interface{} `json:"input_message_content,omitempty"`
}
-// InlineQueryResultDocument is an inline query response document.
-type InlineQueryResultDocument struct {
- // Type of the result, must be document
- //
- // required
+// InlineQueryResultLocation is an inline query response location.
+type InlineQueryResultLocation struct {
+ // Type of the result, must be location
Type string `json:"type"`
- // ID unique identifier for this result, 1-64 bytes
- //
- // required
+ // ID unique identifier for this result, 1-64 Bytes
ID string `json:"id"`
- // Title for the result
- //
- // required
+ // Latitude of the location in degrees
+ Latitude float64 `json:"latitude"`
+ // Longitude of the location in degrees
+ Longitude float64 `json:"longitude"`
+ // Title of the location
Title string `json:"title"`
- // Caption of the document to be sent, 0-1024 characters after entities parsing
+ // HorizontalAccuracy is the radius of uncertainty for the location,
+ // measured in meters; 0-1500
//
// optional
- Caption string `json:"caption"`
- // URL a valid url for the file
+ HorizontalAccuracy float64 `json:"horizontal_accuracy"`
+ // LivePeriod is the period in seconds for which the location can be
+ // updated, should be between 60 and 86400.
//
- // required
- URL string `json:"document_url"`
- // MimeType of the content of the file, either “application/pdf” or “application/zip”
+ // optional
+ LivePeriod int `json:"live_period"`
+ // Heading is for live locations, a direction in which the user is moving,
+ // in degrees. Must be between 1 and 360 if specified.
//
- // required
- MimeType string `json:"mime_type"`
- // Description short description of the result
+ // optional
+ Heading int `json:"heading"`
+ // ProximityAlertRadius is for live locations, a maximum distance for
+ // proximity alerts about approaching another chat member, in meters. Must
+ // be between 1 and 100000 if specified.
//
// optional
- Description string `json:"description"`
- // ReplyMarkup nline keyboard attached to the message
+ ProximityAlertRadius int `json:"proximity_alert_radius"`
+ // ReplyMarkup inline keyboard attached to the message
//
// optional
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
- // InputMessageContent content of the message to be sent instead of the file
+ // InputMessageContent content of the message to be sent instead of the location
//
// optional
InputMessageContent interface{} `json:"input_message_content,omitempty"`
- // ThumbURL url of the thumbnail (jpeg only) for the file
+ // ThumbURL url of the thumbnail for the result
//
// optional
- ThumbURL string `json:"thumb_url"`
+ ThumbURL string `json:"thumb_url,omitempty"`
// ThumbWidth thumbnail width
//
// optional
- ThumbWidth int `json:"thumb_width"`
+ ThumbWidth int `json:"thumb_width,omitempty"`
// ThumbHeight thumbnail height
//
// optional
- ThumbHeight int `json:"thumb_height"`
+ ThumbHeight int `json:"thumb_height,omitempty"`
}
-// InlineQueryResultCachedDocument is an inline query response with cached document.
-type InlineQueryResultCachedDocument struct {
- // Type of the result, must be document
- //
- // required
+// InlineQueryResultMPEG4GIF is an inline query response MPEG4 GIF.
+type InlineQueryResultMPEG4GIF struct {
+ // Type of the result, must be mpeg4_gif
Type string `json:"type"`
// ID unique identifier for this result, 1-64 bytes
- //
- // required
ID string `json:"id"`
- // DocumentID a valid file identifier for the file
+ // URL a valid URL for the MP4 file. File size must not exceed 1MB
+ URL string `json:"mpeg4_url"`
+ // Width video width
+ //
+ // optional
+ Width int `json:"mpeg4_width"`
+ // Height vVideo height
//
- // required
- DocumentID string `json:"document_file_id"`
- // Title for the result
+ // optional
+ Height int `json:"mpeg4_height"`
+ // Duration video duration
//
// optional
- Title string `json:"title"` // required
- // Caption of the document to be sent, 0-1024 characters after entities parsing
+ Duration int `json:"mpeg4_duration"`
+ // ThumbURL url of the static (JPEG or GIF) or animated (MPEG4) thumbnail for the result.
+ ThumbURL string `json:"thumb_url"`
+ // Title for the result
//
// optional
- Caption string `json:"caption"`
- // Description short description of the result
+ Title string `json:"title,omitempty"`
+ // Caption of the MPEG-4 file to be sent, 0-1024 characters after entities parsing.
//
// optional
- Description string `json:"description"`
+ Caption string `json:"caption,omitempty"`
// ParseMode mode for parsing entities in the video caption.
- // // See formatting options for more details
- // // (https://core.telegram.org/bots/api#formatting-options).
+ // See formatting options for more details
+ // (https://core.telegram.org/bots/api#formatting-options).
+ //
+ // optional
+ ParseMode string `json:"parse_mode,omitempty"`
+ // CaptionEntities is a list of special entities that appear in the caption,
+ // which can be specified instead of parse_mode
//
// optional
- ParseMode string `json:"parse_mode"`
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
// ReplyMarkup inline keyboard attached to the message
//
// optional
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
- // InputMessageContent content of the message to be sent instead of the file
+ // InputMessageContent content of the message to be sent instead of the video animation
//
// optional
InputMessageContent interface{} `json:"input_message_content,omitempty"`
}
-// InlineQueryResultLocation is an inline query response location.
-type InlineQueryResultLocation struct {
- // Type of the result, must be location
- //
- // required
+// InlineQueryResultPhoto is an inline query response photo.
+type InlineQueryResultPhoto struct {
+ // Type of the result, must be article.
Type string `json:"type"`
- // ID unique identifier for this result, 1-64 Bytes
- //
- // required
+ // ID unique identifier for this result, 1-64 Bytes.
ID string `json:"id"`
- // Latitude of the location in degrees
+ // URL a valid URL of the photo. Photo must be in jpeg format.
+ // Photo size must not exceed 5MB.
+ URL string `json:"photo_url"`
+ // MimeType
+ MimeType string `json:"mime_type"`
+ // Width of the photo
//
- // required
- Latitude float64 `json:"latitude"`
- // Longitude of the location in degrees
+ // optional
+ Width int `json:"photo_width,omitempty"`
+ // Height of the photo
//
- // required
- Longitude float64 `json:"longitude"`
- // Title of the location
+ // optional
+ Height int `json:"photo_height,omitempty"`
+ // ThumbURL url of the thumbnail for the photo.
//
- // required
- Title string `json:"title"`
- // ReplyMarkup inline keyboard attached to the message
+ // optional
+ ThumbURL string `json:"thumb_url,omitempty"`
+ // Title for the result
//
// optional
- ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
- // InputMessageContent content of the message to be sent instead of the location
+ Title string `json:"title,omitempty"`
+ // Description short description of the result
//
// optional
- InputMessageContent interface{} `json:"input_message_content,omitempty"`
- // ThumbURL url of the thumbnail for the result
+ Description string `json:"description,omitempty"`
+ // Caption of the photo to be sent, 0-1024 characters after entities parsing.
//
// optional
- ThumbURL string `json:"thumb_url"`
- // ThumbWidth thumbnail width
+ Caption string `json:"caption,omitempty"`
+ // ParseMode mode for parsing entities in the photo caption.
+ // See formatting options for more details
+ // (https://core.telegram.org/bots/api#formatting-options).
//
// optional
- ThumbWidth int `json:"thumb_width"`
- // ThumbHeight thumbnail height
+ ParseMode string `json:"parse_mode,omitempty"`
+ // ReplyMarkup inline keyboard attached to the message.
+ //
+ // optional
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+ // CaptionEntities is a list of special entities that appear in the caption,
+ // which can be specified instead of parse_mode
+ //
+ // optional
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
+ // InputMessageContent content of the message to be sent instead of the photo.
//
// optional
- ThumbHeight int `json:"thumb_height"`
+ InputMessageContent interface{} `json:"input_message_content,omitempty"`
}
// InlineQueryResultVenue is an inline query response venue.
type InlineQueryResultVenue struct {
// Type of the result, must be venue
- //
- // required
Type string `json:"type"`
// ID unique identifier for this result, 1-64 Bytes
- //
- // required
ID string `json:"id"`
// Latitude of the venue location in degrees
- //
- // required
Latitude float64 `json:"latitude"`
// Longitude of the venue location in degrees
- //
- // required
Longitude float64 `json:"longitude"`
// Title of the venue
- //
- // required
Title string `json:"title"`
// Address of the venue
- //
- // required
Address string `json:"address"`
// FoursquareID foursquare identifier of the venue if known
//
// optional
- FoursquareID string `json:"foursquare_id"`
+ FoursquareID string `json:"foursquare_id,omitempty"`
// FoursquareType foursquare type of the venue, if known.
// (For example, “arts_entertainment/default”, “arts_entertainment/aquarium” or “food/icecream”.)
//
// optional
- FoursquareType string `json:"foursquare_type"`
+ FoursquareType string `json:"foursquare_type,omitempty"`
+ // GooglePlaceID is the Google Places identifier of the venue
+ //
+ // optional
+ GooglePlaceID string `json:"google_place_id,omitempty"`
+ // GooglePlaceType is the Google Places type of the venue
+ //
+ // optional
+ GooglePlaceType string `json:"google_place_type,omitempty"`
// ReplyMarkup inline keyboard attached to the message
//
// optional
@@ -2077,35 +2704,102 @@ type InlineQueryResultVenue struct {
// ThumbURL url of the thumbnail for the result
//
// optional
- ThumbURL string `json:"thumb_url"`
+ ThumbURL string `json:"thumb_url,omitempty"`
// ThumbWidth thumbnail width
//
// optional
- ThumbWidth int `json:"thumb_width"`
+ ThumbWidth int `json:"thumb_width,omitempty"`
// ThumbHeight thumbnail height
//
// optional
- ThumbHeight int `json:"thumb_height"`
+ ThumbHeight int `json:"thumb_height,omitempty"`
}
-// InlineQueryResultGame is an inline query response game.
-type InlineQueryResultGame struct {
- // Type of the result, must be game
- //
- // required
+// InlineQueryResultVideo is an inline query response video.
+type InlineQueryResultVideo struct {
+ // Type of the result, must be video
Type string `json:"type"`
// ID unique identifier for this result, 1-64 bytes
+ ID string `json:"id"`
+ // URL a valid url for the embedded video player or video file
+ URL string `json:"video_url"`
+ // MimeType of the content of video url, “text/html” or “video/mp4”
+ MimeType string `json:"mime_type"`
+ //
+ // ThumbURL url of the thumbnail (jpeg only) for the video
+ // optional
+ ThumbURL string `json:"thumb_url,omitempty"`
+ // Title for the result
+ Title string `json:"title"`
+ // Caption of the video to be sent, 0-1024 characters after entities parsing
+ //
+ // optional
+ Caption string `json:"caption,omitempty"`
+ // Width video width
+ //
+ // optional
+ Width int `json:"video_width,omitempty"`
+ // Height video height
+ //
+ // optional
+ Height int `json:"video_height,omitempty"`
+ // Duration video duration in seconds
+ //
+ // optional
+ Duration int `json:"video_duration,omitempty"`
+ // Description short description of the result
+ //
+ // optional
+ Description string `json:"description,omitempty"`
+ // ReplyMarkup inline keyboard attached to the message
+ //
+ // optional
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+ // InputMessageContent content of the message to be sent instead of the video.
+ // This field is required if InlineQueryResultVideo is used to send
+ // an HTML-page as a result (e.g., a YouTube video).
//
- // required
+ // optional
+ InputMessageContent interface{} `json:"input_message_content,omitempty"`
+}
+
+// InlineQueryResultVoice is an inline query response voice.
+type InlineQueryResultVoice struct {
+ // Type of the result, must be voice
+ Type string `json:"type"`
+ // ID unique identifier for this result, 1-64 bytes
ID string `json:"id"`
- // GameShortName short name of the game
+ // URL a valid URL for the voice recording
+ URL string `json:"voice_url"`
+ // Title recording title
+ Title string `json:"title"`
+ // Caption 0-1024 characters after entities parsing
//
- // required
- GameShortName string `json:"game_short_name"`
+ // optional
+ Caption string `json:"caption,omitempty"`
+ // ParseMode mode for parsing entities in the video caption.
+ // See formatting options for more details
+ // (https://core.telegram.org/bots/api#formatting-options).
+ //
+ // optional
+ ParseMode string `json:"parse_mode,omitempty"`
+ // CaptionEntities is a list of special entities that appear in the caption,
+ // which can be specified instead of parse_mode
+ //
+ // optional
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
+ // Duration recording duration in seconds
+ //
+ // optional
+ Duration int `json:"voice_duration,omitempty"`
// ReplyMarkup inline keyboard attached to the message
//
// optional
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+ // InputMessageContent content of the message to be sent instead of the voice recording
+ //
+ // optional
+ InputMessageContent interface{} `json:"input_message_content,omitempty"`
}
// ChosenInlineResult is an inline query result chosen by a User
@@ -2117,13 +2811,13 @@ type ChosenInlineResult struct {
// Location sender location, only for bots that require user location
//
// optional
- Location *Location `json:"location"`
+ Location *Location `json:"location,omitempty"`
// InlineMessageID identifier of the sent inline message.
// Available only if there is an inline keyboard attached to the message.
// Will be also received in callback queries and can be used to edit the message.
//
// optional
- InlineMessageID string `json:"inline_message_id"`
+ InlineMessageID string `json:"inline_message_id,omitempty"`
// Query the query that was used to obtain the result
Query string `json:"query"`
}
@@ -2138,11 +2832,16 @@ type InputTextMessageContent struct {
// (https://core.telegram.org/bots/api#formatting-options).
//
// optional
- ParseMode string `json:"parse_mode"`
+ ParseMode string `json:"parse_mode,omitempty"`
+ // Entities is a list of special entities that appear in message text, which
+ // can be specified instead of parse_mode
+ //
+ // optional
+ Entities []MessageEntity `json:"entities,omitempty"`
// DisableWebPagePreview disables link previews for links in the sent message
//
// optional
- DisableWebPagePreview bool `json:"disable_web_page_preview"`
+ DisableWebPagePreview bool `json:"disable_web_page_preview,omitempty"`
}
// InputLocationMessageContent contains a location for displaying
@@ -2152,6 +2851,27 @@ type InputLocationMessageContent struct {
Latitude float64 `json:"latitude"`
// Longitude of the location in degrees
Longitude float64 `json:"longitude"`
+ // HorizontalAccuracy is the radius of uncertainty for the location,
+ // measured in meters; 0-1500
+ //
+ // optional
+ HorizontalAccuracy float64 `json:"horizontal_accuracy"`
+ // LivePeriod is the period in seconds for which the location can be
+ // updated, should be between 60 and 86400
+ //
+ // optional
+ LivePeriod int `json:"live_period,omitempty"`
+ // Heading is for live locations, a direction in which the user is moving,
+ // in degrees. Must be between 1 and 360 if specified.
+ //
+ // optional
+ Heading int `json:"heading"`
+ // ProximityAlertRadius is for live locations, a maximum distance for
+ // proximity alerts about approaching another chat member, in meters. Must
+ // be between 1 and 100000 if specified.
+ //
+ // optional
+ ProximityAlertRadius int `json:"proximity_alert_radius"`
}
// InputVenueMessageContent contains a venue for displaying
@@ -2168,7 +2888,19 @@ type InputVenueMessageContent struct {
// FoursquareID foursquare identifier of the venue, if known
//
// optional
- FoursquareID string `json:"foursquare_id"`
+ FoursquareID string `json:"foursquare_id,omitempty"`
+ // FoursquareType Foursquare type of the venue, if known
+ //
+ // optional
+ FoursquareType string `json:"foursquare_type,omitempty"`
+ // GooglePlaceID is the Google Places identifier of the venue
+ //
+ // optional
+ GooglePlaceID string `json:"google_place_id"`
+ // GooglePlaceType is the Google Places type of the venue
+ //
+ // optional
+ GooglePlaceType string `json:"google_place_type"`
}
// InputContactMessageContent contains a contact for displaying
@@ -2181,7 +2913,107 @@ type InputContactMessageContent struct {
// LastName contact's last name
//
// optional
- LastName string `json:"last_name"`
+ LastName string `json:"last_name,omitempty"`
+ // Additional data about the contact in the form of a vCard
+ //
+ // optional
+ VCard string `json:"vcard,omitempty"`
+}
+
+// InputInvoiceMessageContent represents the content of an invoice message to be
+// sent as the result of an inline query.
+type InputInvoiceMessageContent struct {
+ // Product name, 1-32 characters
+ Title string `json:"title"`
+ // Product description, 1-255 characters
+ Description string `json:"description"`
+ // Bot-defined invoice payload, 1-128 bytes. This will not be displayed to
+ // the user, use for your internal processes.
+ Payload string `json:"payload"`
+ // Payment provider token, obtained via Botfather
+ ProviderToken string `json:"provider_token"`
+ // Three-letter ISO 4217 currency code
+ Currency string `json:"currency"`
+ // Price breakdown, a JSON-serialized list of components (e.g. product
+ // price, tax, discount, delivery cost, delivery tax, bonus, etc.)
+ Prices []LabeledPrice `json:"prices"`
+ // The maximum accepted amount for tips in the smallest units of the
+ // currency (integer, not float/double).
+ //
+ // optional
+ MaxTipAmount int `json:"max_tip_amount,omitempty"`
+ // An array of suggested amounts of tip in the smallest units of the
+ // currency (integer, not float/double). At most 4 suggested tip amounts can
+ // be specified. The suggested tip amounts must be positive, passed in a
+ // strictly increased order and must not exceed max_tip_amount.
+ //
+ // optional
+ SuggestedTipAmounts []int `json:"suggested_tip_amounts,omitempty"`
+ // A JSON-serialized object for data about the invoice, which will be shared
+ // with the payment provider. A detailed description of the required fields
+ // should be provided by the payment provider.
+ //
+ // optional
+ ProviderData string `json:"provider_data,omitempty"`
+ // URL of the product photo for the invoice. Can be a photo of the goods or
+ // a marketing image for a service. People like it better when they see what
+ // they are paying for.
+ //
+ // optional
+ PhotoURL string `json:"photo_url,omitempty"`
+ // Photo size
+ //
+ // optional
+ PhotoSize int `json:"photo_size,omitempty"`
+ // Photo width
+ //
+ // optional
+ PhotoWidth int `json:"photo_width,omitempty"`
+ // Photo height
+ //
+ // optional
+ PhotoHeight int `json:"photo_height,omitempty"`
+ // Pass True, if you require the user's full name to complete the order
+ //
+ // optional
+ NeedName bool `json:"need_name,omitempty"`
+ // Pass True, if you require the user's phone number to complete the order
+ //
+ // optional
+ NeedPhoneNumber bool `json:"need_phone_number,omitempty"`
+ // Pass True, if you require the user's email address to complete the order
+ //
+ // optional
+ NeedEmail bool `json:"need_email,omitempty"`
+ // Pass True, if you require the user's shipping address to complete the order
+ //
+ // optional
+ NeedShippingAddress bool `json:"need_shipping_address,omitempty"`
+ // Pass True, if user's phone number should be sent to provider
+ //
+ // optional
+ SendPhoneNumberToProvider bool `json:"send_phone_number_to_provider,omitempty"`
+ // Pass True, if user's email address should be sent to provider
+ //
+ // optional
+ SendEmailToProvider bool `json:"send_email_to_provider,omitempty"`
+ // Pass True, if the final price depends on the shipping method
+ //
+ // optional
+ IsFlexible bool `json:"is_flexible,omitempty"`
+}
+
+// LabeledPrice represents a portion of the price for goods or services.
+type LabeledPrice struct {
+ // Label portion label
+ Label string `json:"label"`
+ // Amount price of the product in the smallest units of the currency (integer, not float/double).
+ // For example, for a price of US$ 1.45 pass amount = 145.
+ // See the exp parameter in currencies.json
+ // (https://core.telegram.org/bots/payments/currencies.json),
+ // it shows the number of digits past the decimal point
+ // for each currency (2 for the majority of currencies).
+ Amount int `json:"amount"`
}
// Invoice contains basic information about an invoice.
@@ -2204,19 +3036,6 @@ type Invoice struct {
TotalAmount int `json:"total_amount"`
}
-// LabeledPrice represents a portion of the price for goods or services.
-type LabeledPrice struct {
- // Label portion label
- Label string `json:"label"`
- // Amount price of the product in the smallest units of the currency (integer, not float/double).
- // For example, for a price of US$ 1.45 pass amount = 145.
- // See the exp parameter in currencies.json
- // (https://core.telegram.org/bots/payments/currencies.json),
- // it shows the number of digits past the decimal point
- // for each currency (2 for the majority of currencies).
- Amount int `json:"amount"`
-}
-
// ShippingAddress represents a shipping address.
type ShippingAddress struct {
// CountryCode ISO 3166-1 alpha-2 country code
@@ -2260,7 +3079,7 @@ type ShippingOption struct {
// Title option title
Title string `json:"title"`
// Prices list of price portions
- Prices *[]LabeledPrice `json:"prices"`
+ Prices []LabeledPrice `json:"prices"`
}
// SuccessfulPayment contains basic information about a successful payment.
@@ -2330,23 +3149,3 @@ type PreCheckoutQuery struct {
// optional
OrderInfo *OrderInfo `json:"order_info,omitempty"`
}
-
-// Error is an error containing extra information returned by the Telegram API.
-type Error struct {
- Code int
- Message string
- ResponseParameters
-}
-
-func (e Error) Error() string {
- return e.Message
-}
-
-// BotCommand represents a bot command.
-type BotCommand struct {
- // Command text of the command, 1-32 characters.
- // Can contain only lowercase English letters, digits and underscores.
- Command string `json:"command"`
- // Description of the command, 3-256 characters.
- Description string `json:"description"`
-}
diff --git a/types_test.go b/types_test.go
index a5db1d4f..77425561 100644
--- a/types_test.go
+++ b/types_test.go
@@ -1,14 +1,12 @@
-package tgbotapi_test
+package tgbotapi
import (
"testing"
"time"
-
- "github.com/go-telegram-bot-api/telegram-bot-api"
)
func TestUserStringWith(t *testing.T) {
- user := tgbotapi.User{
+ user := User{
ID: 0,
FirstName: "Test",
LastName: "Test",
@@ -23,7 +21,7 @@ func TestUserStringWith(t *testing.T) {
}
func TestUserStringWithUserName(t *testing.T) {
- user := tgbotapi.User{
+ user := User{
ID: 0,
FirstName: "Test",
LastName: "Test",
@@ -37,7 +35,7 @@ func TestUserStringWithUserName(t *testing.T) {
}
func TestMessageTime(t *testing.T) {
- message := tgbotapi.Message{Date: 0}
+ message := Message{Date: 0}
date := time.Unix(0, 0)
if message.Time() != date {
@@ -46,33 +44,33 @@ func TestMessageTime(t *testing.T) {
}
func TestMessageIsCommandWithCommand(t *testing.T) {
- message := tgbotapi.Message{Text: "/command"}
- message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
+ message := Message{Text: "/command"}
+ message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
- if message.IsCommand() != true {
+ if !message.IsCommand() {
t.Fail()
}
}
func TestIsCommandWithText(t *testing.T) {
- message := tgbotapi.Message{Text: "some text"}
+ message := Message{Text: "some text"}
- if message.IsCommand() != false {
+ if message.IsCommand() {
t.Fail()
}
}
func TestIsCommandWithEmptyText(t *testing.T) {
- message := tgbotapi.Message{Text: ""}
+ message := Message{Text: ""}
- if message.IsCommand() != false {
+ if message.IsCommand() {
t.Fail()
}
}
func TestCommandWithCommand(t *testing.T) {
- message := tgbotapi.Message{Text: "/command"}
- message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
+ message := Message{Text: "/command"}
+ message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
if message.Command() != "command" {
t.Fail()
@@ -80,7 +78,7 @@ func TestCommandWithCommand(t *testing.T) {
}
func TestCommandWithEmptyText(t *testing.T) {
- message := tgbotapi.Message{Text: ""}
+ message := Message{Text: ""}
if message.Command() != "" {
t.Fail()
@@ -88,7 +86,7 @@ func TestCommandWithEmptyText(t *testing.T) {
}
func TestCommandWithNonCommand(t *testing.T) {
- message := tgbotapi.Message{Text: "test text"}
+ message := Message{Text: "test text"}
if message.Command() != "" {
t.Fail()
@@ -96,8 +94,8 @@ func TestCommandWithNonCommand(t *testing.T) {
}
func TestCommandWithBotName(t *testing.T) {
- message := tgbotapi.Message{Text: "/command@testbot"}
- message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 16}}
+ message := Message{Text: "/command@testbot"}
+ message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 16}}
if message.Command() != "command" {
t.Fail()
@@ -105,8 +103,8 @@ func TestCommandWithBotName(t *testing.T) {
}
func TestCommandWithAtWithBotName(t *testing.T) {
- message := tgbotapi.Message{Text: "/command@testbot"}
- message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 16}}
+ message := Message{Text: "/command@testbot"}
+ message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 16}}
if message.CommandWithAt() != "command@testbot" {
t.Fail()
@@ -114,37 +112,37 @@ func TestCommandWithAtWithBotName(t *testing.T) {
}
func TestMessageCommandArgumentsWithArguments(t *testing.T) {
- message := tgbotapi.Message{Text: "/command with arguments"}
- message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
+ message := Message{Text: "/command with arguments"}
+ message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
if message.CommandArguments() != "with arguments" {
t.Fail()
}
}
func TestMessageCommandArgumentsWithMalformedArguments(t *testing.T) {
- message := tgbotapi.Message{Text: "/command-without argument space"}
- message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
+ message := Message{Text: "/command-without argument space"}
+ message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
if message.CommandArguments() != "without argument space" {
t.Fail()
}
}
func TestMessageCommandArgumentsWithoutArguments(t *testing.T) {
- message := tgbotapi.Message{Text: "/command"}
+ message := Message{Text: "/command"}
if message.CommandArguments() != "" {
t.Fail()
}
}
func TestMessageCommandArgumentsForNonCommand(t *testing.T) {
- message := tgbotapi.Message{Text: "test text"}
+ message := Message{Text: "test text"}
if message.CommandArguments() != "" {
t.Fail()
}
}
func TestMessageEntityParseURLGood(t *testing.T) {
- entity := tgbotapi.MessageEntity{URL: "https://www.google.com"}
+ entity := MessageEntity{URL: "https://www.google.com"}
if _, err := entity.ParseURL(); err != nil {
t.Fail()
@@ -152,7 +150,7 @@ func TestMessageEntityParseURLGood(t *testing.T) {
}
func TestMessageEntityParseURLBad(t *testing.T) {
- entity := tgbotapi.MessageEntity{URL: ""}
+ entity := MessageEntity{URL: ""}
if _, err := entity.ParseURL(); err == nil {
t.Fail()
@@ -160,31 +158,31 @@ func TestMessageEntityParseURLBad(t *testing.T) {
}
func TestChatIsPrivate(t *testing.T) {
- chat := tgbotapi.Chat{ID: 10, Type: "private"}
+ chat := Chat{ID: 10, Type: "private"}
- if chat.IsPrivate() != true {
+ if !chat.IsPrivate() {
t.Fail()
}
}
func TestChatIsGroup(t *testing.T) {
- chat := tgbotapi.Chat{ID: 10, Type: "group"}
+ chat := Chat{ID: 10, Type: "group"}
- if chat.IsGroup() != true {
+ if !chat.IsGroup() {
t.Fail()
}
}
func TestChatIsChannel(t *testing.T) {
- chat := tgbotapi.Chat{ID: 10, Type: "channel"}
+ chat := Chat{ID: 10, Type: "channel"}
- if chat.IsChannel() != true {
+ if !chat.IsChannel() {
t.Fail()
}
}
func TestChatIsSuperGroup(t *testing.T) {
- chat := tgbotapi.Chat{ID: 10, Type: "supergroup"}
+ chat := Chat{ID: 10, Type: "supergroup"}
if !chat.IsSuperGroup() {
t.Fail()
@@ -192,7 +190,7 @@ func TestChatIsSuperGroup(t *testing.T) {
}
func TestMessageEntityIsMention(t *testing.T) {
- entity := tgbotapi.MessageEntity{Type: "mention"}
+ entity := MessageEntity{Type: "mention"}
if !entity.IsMention() {
t.Fail()
@@ -200,7 +198,7 @@ func TestMessageEntityIsMention(t *testing.T) {
}
func TestMessageEntityIsHashtag(t *testing.T) {
- entity := tgbotapi.MessageEntity{Type: "hashtag"}
+ entity := MessageEntity{Type: "hashtag"}
if !entity.IsHashtag() {
t.Fail()
@@ -208,7 +206,7 @@ func TestMessageEntityIsHashtag(t *testing.T) {
}
func TestMessageEntityIsBotCommand(t *testing.T) {
- entity := tgbotapi.MessageEntity{Type: "bot_command"}
+ entity := MessageEntity{Type: "bot_command"}
if !entity.IsCommand() {
t.Fail()
@@ -216,15 +214,15 @@ func TestMessageEntityIsBotCommand(t *testing.T) {
}
func TestMessageEntityIsUrl(t *testing.T) {
- entity := tgbotapi.MessageEntity{Type: "url"}
+ entity := MessageEntity{Type: "url"}
- if !entity.IsUrl() {
+ if !entity.IsURL() {
t.Fail()
}
}
func TestMessageEntityIsEmail(t *testing.T) {
- entity := tgbotapi.MessageEntity{Type: "email"}
+ entity := MessageEntity{Type: "email"}
if !entity.IsEmail() {
t.Fail()
@@ -232,7 +230,7 @@ func TestMessageEntityIsEmail(t *testing.T) {
}
func TestMessageEntityIsBold(t *testing.T) {
- entity := tgbotapi.MessageEntity{Type: "bold"}
+ entity := MessageEntity{Type: "bold"}
if !entity.IsBold() {
t.Fail()
@@ -240,7 +238,7 @@ func TestMessageEntityIsBold(t *testing.T) {
}
func TestMessageEntityIsItalic(t *testing.T) {
- entity := tgbotapi.MessageEntity{Type: "italic"}
+ entity := MessageEntity{Type: "italic"}
if !entity.IsItalic() {
t.Fail()
@@ -248,7 +246,7 @@ func TestMessageEntityIsItalic(t *testing.T) {
}
func TestMessageEntityIsCode(t *testing.T) {
- entity := tgbotapi.MessageEntity{Type: "code"}
+ entity := MessageEntity{Type: "code"}
if !entity.IsCode() {
t.Fail()
@@ -256,7 +254,7 @@ func TestMessageEntityIsCode(t *testing.T) {
}
func TestMessageEntityIsPre(t *testing.T) {
- entity := tgbotapi.MessageEntity{Type: "pre"}
+ entity := MessageEntity{Type: "pre"}
if !entity.IsPre() {
t.Fail()
@@ -264,7 +262,7 @@ func TestMessageEntityIsPre(t *testing.T) {
}
func TestMessageEntityIsTextLink(t *testing.T) {
- entity := tgbotapi.MessageEntity{Type: "text_link"}
+ entity := MessageEntity{Type: "text_link"}
if !entity.IsTextLink() {
t.Fail()
@@ -272,9 +270,104 @@ func TestMessageEntityIsTextLink(t *testing.T) {
}
func TestFileLink(t *testing.T) {
- file := tgbotapi.File{FilePath: "test/test.txt"}
+ file := File{FilePath: "test/test.txt"}
if file.Link("token") != "https://api.telegram.org/file/bottoken/test/test.txt" {
t.Fail()
}
}
+
+// Ensure all configs are sendable
+var (
+ _ Chattable = AnimationConfig{}
+ _ Chattable = AudioConfig{}
+ _ Chattable = CallbackConfig{}
+ _ Chattable = ChatActionConfig{}
+ _ Chattable = ChatAdministratorsConfig{}
+ _ Chattable = ChatInfoConfig{}
+ _ Chattable = ChatInviteLinkConfig{}
+ _ Chattable = CloseConfig{}
+ _ Chattable = ContactConfig{}
+ _ Chattable = CopyMessageConfig{}
+ _ Chattable = CreateChatInviteLinkConfig{}
+ _ Chattable = DeleteChatPhotoConfig{}
+ _ Chattable = DeleteChatStickerSetConfig{}
+ _ Chattable = DeleteMessageConfig{}
+ _ Chattable = DeleteMyCommandsConfig{}
+ _ Chattable = DeleteWebhookConfig{}
+ _ Chattable = DocumentConfig{}
+ _ Chattable = EditChatInviteLinkConfig{}
+ _ Chattable = EditMessageCaptionConfig{}
+ _ Chattable = EditMessageLiveLocationConfig{}
+ _ Chattable = EditMessageMediaConfig{}
+ _ Chattable = EditMessageReplyMarkupConfig{}
+ _ Chattable = EditMessageTextConfig{}
+ _ Chattable = FileConfig{}
+ _ Chattable = ForwardConfig{}
+ _ Chattable = GameConfig{}
+ _ Chattable = GetChatMemberConfig{}
+ _ Chattable = GetGameHighScoresConfig{}
+ _ Chattable = InlineConfig{}
+ _ Chattable = InvoiceConfig{}
+ _ Chattable = KickChatMemberConfig{}
+ _ Chattable = LeaveChatConfig{}
+ _ Chattable = LocationConfig{}
+ _ Chattable = LogOutConfig{}
+ _ Chattable = MediaGroupConfig{}
+ _ Chattable = MessageConfig{}
+ _ Chattable = PhotoConfig{}
+ _ Chattable = PinChatMessageConfig{}
+ _ Chattable = PreCheckoutConfig{}
+ _ Chattable = PromoteChatMemberConfig{}
+ _ Chattable = RestrictChatMemberConfig{}
+ _ Chattable = RevokeChatInviteLinkConfig{}
+ _ Chattable = SendPollConfig{}
+ _ Chattable = SetChatDescriptionConfig{}
+ _ Chattable = SetChatPhotoConfig{}
+ _ Chattable = SetChatTitleConfig{}
+ _ Chattable = SetGameScoreConfig{}
+ _ Chattable = ShippingConfig{}
+ _ Chattable = StickerConfig{}
+ _ Chattable = StopMessageLiveLocationConfig{}
+ _ Chattable = StopPollConfig{}
+ _ Chattable = UnbanChatMemberConfig{}
+ _ Chattable = UnpinChatMessageConfig{}
+ _ Chattable = UpdateConfig{}
+ _ Chattable = UserProfilePhotosConfig{}
+ _ Chattable = VenueConfig{}
+ _ Chattable = VideoConfig{}
+ _ Chattable = VideoNoteConfig{}
+ _ Chattable = VoiceConfig{}
+ _ Chattable = WebhookConfig{}
+)
+
+// Ensure all Fileable types are correct.
+var (
+ _ Fileable = (*PhotoConfig)(nil)
+ _ Fileable = (*AudioConfig)(nil)
+ _ Fileable = (*DocumentConfig)(nil)
+ _ Fileable = (*StickerConfig)(nil)
+ _ Fileable = (*VideoConfig)(nil)
+ _ Fileable = (*AnimationConfig)(nil)
+ _ Fileable = (*VideoNoteConfig)(nil)
+ _ Fileable = (*VoiceConfig)(nil)
+ _ Fileable = (*SetChatPhotoConfig)(nil)
+ _ Fileable = (*EditMessageMediaConfig)(nil)
+ _ Fileable = (*SetChatPhotoConfig)(nil)
+ _ Fileable = (*UploadStickerConfig)(nil)
+ _ Fileable = (*NewStickerSetConfig)(nil)
+ _ Fileable = (*AddStickerConfig)(nil)
+ _ Fileable = (*MediaGroupConfig)(nil)
+ _ Fileable = (*WebhookConfig)(nil)
+ _ Fileable = (*SetStickerSetThumbConfig)(nil)
+)
+
+// Ensure all RequestFileData types are correct.
+var (
+ _ RequestFileData = (*FilePath)(nil)
+ _ RequestFileData = (*FileBytes)(nil)
+ _ RequestFileData = (*FileReader)(nil)
+ _ RequestFileData = (*FileURL)(nil)
+ _ RequestFileData = (*FileID)(nil)
+ _ RequestFileData = (*fileAttach)(nil)
+)