diff options
Diffstat (limited to 'vendor/github.com/matterbridge/telegram-bot-api')
11 files changed, 9035 insertions, 0 deletions
diff --git a/vendor/github.com/matterbridge/telegram-bot-api/v6/.gitignore b/vendor/github.com/matterbridge/telegram-bot-api/v6/.gitignore new file mode 100644 index 00000000..d3083e5f --- /dev/null +++ b/vendor/github.com/matterbridge/telegram-bot-api/v6/.gitignore @@ -0,0 +1,5 @@ +.idea/ +coverage.out +tmp/ +book/ +.vscode/ diff --git a/vendor/github.com/matterbridge/telegram-bot-api/v6/LICENSE.txt b/vendor/github.com/matterbridge/telegram-bot-api/v6/LICENSE.txt new file mode 100644 index 00000000..b1fef93b --- /dev/null +++ b/vendor/github.com/matterbridge/telegram-bot-api/v6/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Syfaro + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/matterbridge/telegram-bot-api/v6/README.md b/vendor/github.com/matterbridge/telegram-bot-api/v6/README.md new file mode 100644 index 00000000..b18d15dd --- /dev/null +++ b/vendor/github.com/matterbridge/telegram-bot-api/v6/README.md @@ -0,0 +1,121 @@ +# Golang bindings for the Telegram Bot API + +[![Go Reference](https://pkg.go.dev/badge/github.com/go-telegram-bot-api/telegram-bot-api/v5.svg)](https://pkg.go.dev/github.com/go-telegram-bot-api/telegram-bot-api/v5) +[![Test](https://github.com/go-telegram-bot-api/telegram-bot-api/actions/workflows/test.yml/badge.svg)](https://github.com/go-telegram-bot-api/telegram-bot-api/actions/workflows/test.yml) + +All methods are fairly self-explanatory, and reading the [godoc](https://pkg.go.dev/github.com/go-telegram-bot-api/telegram-bot-api/v5) page should +explain everything. If something isn't clear, open an issue or submit +a pull request. + +There are more tutorials and high-level information on the website, [go-telegram-bot-api.dev](https://go-telegram-bot-api.dev). + +The scope of this project is just to provide a wrapper around the API +without any additional features. There are other projects for creating +something with plugins and command handlers without having to design +all that yourself. + +Join [the development group](https://telegram.me/go_telegram_bot_api) if +you want to ask questions or discuss development. + +## Example + +First, ensure the library is installed and up to date by running +`go get -u github.com/go-telegram-bot-api/telegram-bot-api/v5`. + +This is a very simple bot that just displays any gotten updates, +then replies it to that chat. + +```go +package main + +import ( + "log" + + tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" +) + +func main() { + bot, err := tgbotapi.NewBotAPI("MyAwesomeBotToken") + 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 { // If we got a message + log.Printf("[%s] %s", update.Message.From.UserName, update.Message.Text) + + msg := tgbotapi.NewMessage(update.Message.Chat.ID, update.Message.Text) + msg.ReplyToMessageID = update.Message.MessageID + + bot.Send(msg) + } + } +} +``` + +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/v5" +) + +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) + + wh, _ := tgbotapi.NewWebhookWithCert("https://www.example.com:8443/"+bot.Token, "cert.pem") + + _, err = bot.Request(wh) + 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 certificate, 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/vendor/github.com/matterbridge/telegram-bot-api/v6/book.toml b/vendor/github.com/matterbridge/telegram-bot-api/v6/book.toml new file mode 100644 index 00000000..841d5ba6 --- /dev/null +++ b/vendor/github.com/matterbridge/telegram-bot-api/v6/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/vendor/github.com/matterbridge/telegram-bot-api/v6/bot.go b/vendor/github.com/matterbridge/telegram-bot-api/v6/bot.go new file mode 100644 index 00000000..39037b8d --- /dev/null +++ b/vendor/github.com/matterbridge/telegram-bot-api/v6/bot.go @@ -0,0 +1,748 @@ +// Package tgbotapi has functions and types used for interacting with +// the Telegram Bot API. +package tgbotapi + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/url" + "strings" + "time" +) + +// HTTPClient is the type needed for the bot to perform HTTP requests. +type HTTPClient interface { + Do(req *http.Request) (*http.Response, error) +} + +// BotAPI allows you to interact with the Telegram Bot API. +type BotAPI struct { + Token string `json:"token"` + Debug bool `json:"debug"` + Buffer int `json:"buffer"` + + Self User `json:"-"` + Client HTTPClient `json:"-"` + shutdownChannel chan interface{} + + apiEndpoint string +} + +// NewBotAPI creates a new BotAPI instance. +// +// It requires a token, provided by @BotFather on Telegram. +func NewBotAPI(token string) (*BotAPI, error) { + return NewBotAPIWithClient(token, APIEndpoint, &http.Client{}) +} + +// NewBotAPIWithAPIEndpoint creates a new BotAPI instance +// and allows you to pass API endpoint. +// +// It requires a token, provided by @BotFather on Telegram and API endpoint. +func NewBotAPIWithAPIEndpoint(token, apiEndpoint string) (*BotAPI, error) { + return NewBotAPIWithClient(token, apiEndpoint, &http.Client{}) +} + +// NewBotAPIWithClient creates a new BotAPI instance +// 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) { + bot := &BotAPI{ + Token: token, + Client: client, + Buffer: 100, + shutdownChannel: make(chan interface{}), + + apiEndpoint: apiEndpoint, + } + + self, err := bot.GetMe() + if err != nil { + return nil, err + } + + bot.Self = self + + return bot, nil +} + +// 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 Params) (*APIResponse, error) { + if bot.Debug { + log.Printf("Endpoint: %s, params: %v\n", endpoint, params) + } + + method := fmt.Sprintf(bot.apiEndpoint, bot.Token, endpoint) + + values := buildParams(params) + + req, err := http.NewRequest("POST", method, strings.NewReader(values.Encode())) + if err != nil { + return &APIResponse{}, err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + resp, err := bot.Client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var apiResp APIResponse + bytes, err := bot.decodeAPIResponse(resp.Body, &apiResp) + if err != nil { + return &apiResp, err + } + + if bot.Debug { + log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes)) + } + + if !apiResp.Ok { + var parameters ResponseParameters + + if apiResp.Parameters != nil { + parameters = *apiResp.Parameters + } + + return &apiResp, &Error{ + Code: apiResp.ErrorCode, + Message: apiResp.Description, + ResponseParameters: parameters, + } + } + + return &apiResp, nil +} + +// decodeAPIResponse decode response and return slice of bytes if debug enabled. +// If debug disabled, just decode http.Response.Body stream to APIResponse struct +// for efficient memory usage +func (bot *BotAPI) decodeAPIResponse(responseBody io.Reader, resp *APIResponse) ([]byte, error) { + if !bot.Debug { + dec := json.NewDecoder(responseBody) + err := dec.Decode(resp) + return nil, err + } + + // if debug, read response body + data, err := io.ReadAll(responseBody) + if err != nil { + return nil, err + } + + err = json.Unmarshal(data, resp) + if err != nil { + return nil, err + } + + return data, nil +} + +// 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) + + // 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() + + for field, value := range params { + if err := m.WriteField(field, value); err != nil { + w.CloseWithError(err) + return + } + } + + for _, file := range files { + if file.Data.NeedsUpload() { + name, reader, err := file.Data.UploadData() + if err != nil { + w.CloseWithError(err) + return + } + + part, err := m.CreateFormFile(file.Name, name) + if err != nil { + w.CloseWithError(err) + return + } + + if _, err := io.Copy(part, reader); err != nil { + w.CloseWithError(err) + return + } + + if closer, ok := reader.(io.ReadCloser); ok { + if err = closer.Close(); err != nil { + w.CloseWithError(err) + return + } + } + } else { + value := file.Data.SendData() + + if err := m.WriteField(file.Name, value); err != nil { + w.CloseWithError(err) + return + } + } + } + }() + + 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, r) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", m.FormDataContentType()) + + resp, err := bot.Client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var apiResp APIResponse + bytes, err := bot.decodeAPIResponse(resp.Body, &apiResp) + if err != nil { + return &apiResp, err + } + + if bot.Debug { + log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes)) + } + + if !apiResp.Ok { + var parameters ResponseParameters + + if apiResp.Parameters != nil { + parameters = *apiResp.Parameters + } + + return &apiResp, &Error{ + Message: apiResp.Description, + ResponseParameters: parameters, + } + } + + return &apiResp, nil +} + +// GetFileDirectURL returns direct URL to file +// +// It requires the FileID. +func (bot *BotAPI) GetFileDirectURL(fileID string) (string, error) { + file, err := bot.GetFile(FileConfig{fileID}) + + if err != nil { + return "", err + } + + return file.Link(bot.Token), nil +} + +// GetMe fetches the currently authenticated bot. +// +// This method is called upon creation to validate the token, +// and so you may get this data from BotAPI.Self without the need for +// another request. +func (bot *BotAPI) GetMe() (User, error) { + resp, err := bot.MakeRequest("getMe", nil) + if err != nil { + return User{}, err + } + + var user User + err = json.Unmarshal(resp.Result, &user) + + return user, err +} + +// IsMessageToMe returns true if message directed to this bot. +// +// It requires the Message. +func (bot *BotAPI) IsMessageToMe(message Message) bool { + return strings.Contains(message.Text, "@"+bot.Self.UserName) +} + +func hasFilesNeedingUpload(files []RequestFile) bool { + for _, file := range files { + if file.Data.NeedsUpload() { + return true + } + } + + return false +} + +// 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 nil, err + } + + if t, ok := c.(Fileable); ok { + files := t.files() + + // 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) + } + + // 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() + } + } + + return bot.MakeRequest(c.method(), params) +} + +// 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 + err = json.Unmarshal(resp.Result, &message) + + return message, err +} + +// 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 nil, err + } + + var messages []Message + err = json.Unmarshal(resp.Result, &messages) + + return messages, err +} + +// GetUserProfilePhotos gets a user's profile photos. +// +// It requires UserID. +// Offset and Limit are optional. +func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) { + resp, err := bot.Request(config) + if err != nil { + return UserProfilePhotos{}, err + } + + var profilePhotos UserProfilePhotos + err = json.Unmarshal(resp.Result, &profilePhotos) + + return profilePhotos, err +} + +// GetFile returns a File which can download a file from Telegram. +// +// Requires FileID. +func (bot *BotAPI) GetFile(config FileConfig) (File, error) { + resp, err := bot.Request(config) + if err != nil { + return File{}, err + } + + var file File + err = json.Unmarshal(resp.Result, &file) + + return file, err +} + +// GetUpdates fetches updates. +// If a WebHook is set, this will not return any data! +// +// 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) { + resp, err := bot.Request(config) + if err != nil { + return []Update{}, err + } + + var updates []Update + err = json.Unmarshal(resp.Result, &updates) + + 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", nil) + if err != nil { + return WebhookInfo{}, err + } + + var info WebhookInfo + err = json.Unmarshal(resp.Result, &info) + + return info, err +} + +// GetUpdatesChan starts and returns a channel for getting updates. +func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) UpdatesChannel { + ch := make(chan Update, bot.Buffer) + + go func() { + for { + select { + case <-bot.shutdownChannel: + close(ch) + return + default: + } + + updates, err := bot.GetUpdates(config) + if err != nil { + log.Println(err) + log.Println("Failed to get updates, retrying in 3 seconds...") + time.Sleep(time.Second * 3) + + continue + } + + for _, update := range updates { + if update.UpdateID >= config.Offset { + config.Offset = update.UpdateID + 1 + ch <- update + } + } + } + }() + + return ch +} + +// StopReceivingUpdates stops the go routine which receives updates +func (bot *BotAPI) StopReceivingUpdates() { + if bot.Debug { + log.Println("Stopping the update receiver routine...") + } + close(bot.shutdownChannel) +} + +// ListenForWebhook registers a http handler for a webhook. +func (bot *BotAPI) ListenForWebhook(pattern string) UpdatesChannel { + ch := make(chan Update, bot.Buffer) + + http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) { + update, err := bot.HandleUpdate(r) + if err != nil { + errMsg, _ := json.Marshal(map[string]string{"error": err.Error()}) + w.WriteHeader(http.StatusBadRequest) + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write(errMsg) + return + } + + ch <- *update + }) + + return ch +} + +// ListenForWebhookRespReqFormat registers a http handler for a single incoming webhook. +func (bot *BotAPI) ListenForWebhookRespReqFormat(w http.ResponseWriter, r *http.Request) UpdatesChannel { + ch := make(chan Update, bot.Buffer) + + func(w http.ResponseWriter, r *http.Request) { + defer close(ch) + + update, err := bot.HandleUpdate(r) + if err != nil { + errMsg, _ := json.Marshal(map[string]string{"error": err.Error()}) + w.WriteHeader(http.StatusBadRequest) + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write(errMsg) + return + } + + ch <- *update + }(w, r) + + return ch +} + +// HandleUpdate parses and returns update received via webhook +func (bot *BotAPI) HandleUpdate(r *http.Request) (*Update, error) { + if r.Method != http.MethodPost { + err := errors.New("wrong HTTP method required POST") + return nil, err + } + + var update Update + err := json.NewDecoder(r.Body).Decode(&update) + if err != nil { + return nil, err + } + + return &update, nil +} + +// WriteToHTTPResponse writes the request to the HTTP ResponseWriter. +// +// 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 err + } + + if t, ok := c.(Fileable); ok { + if hasFilesNeedingUpload(t.files()) { + return errors.New("unable to use http response to upload files") + } + } + + values := buildParams(params) + values.Set("method", c.method()) + + 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 ChatInfoConfig) (Chat, error) { + resp, err := bot.Request(config) + if err != nil { + return Chat{}, err + } + + var chat Chat + err = json.Unmarshal(resp.Result, &chat) + + return chat, err +} + +// GetChatAdministrators gets a list of administrators in the chat. +// +// 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 ChatAdministratorsConfig) ([]ChatMember, error) { + resp, err := bot.Request(config) + if err != nil { + return []ChatMember{}, err + } + + var members []ChatMember + err = json.Unmarshal(resp.Result, &members) + + return members, err +} + +// GetChatMembersCount gets the number of users in a chat. +func (bot *BotAPI) GetChatMembersCount(config ChatMemberCountConfig) (int, error) { + resp, err := bot.Request(config) + if err != nil { + return -1, err + } + + var count int + err = json.Unmarshal(resp.Result, &count) + + return count, err +} + +// GetChatMember gets a specific chat member. +func (bot *BotAPI) GetChatMember(config GetChatMemberConfig) (ChatMember, error) { + resp, err := bot.Request(config) + if err != nil { + return ChatMember{}, err + } + + var member ChatMember + err = json.Unmarshal(resp.Result, &member) + + return member, err +} + +// GetGameHighScores allows you to get the high scores for a game. +func (bot *BotAPI) GetGameHighScores(config GetGameHighScoresConfig) ([]GameHighScore, error) { + resp, err := bot.Request(config) + if err != nil { + return []GameHighScore{}, err + } + + var highScores []GameHighScore + err = json.Unmarshal(resp.Result, &highScores) + + return highScores, err +} + +// GetInviteLink get InviteLink for a chat +func (bot *BotAPI) GetInviteLink(config ChatInviteLinkConfig) (string, error) { + resp, err := bot.Request(config) + if err != nil { + return "", err + } + + var inviteLink string + err = json.Unmarshal(resp.Result, &inviteLink) + + return inviteLink, err +} + +// GetStickerSet returns a StickerSet. +func (bot *BotAPI) GetStickerSet(config GetStickerSetConfig) (StickerSet, error) { + resp, err := bot.Request(config) + if err != nil { + return StickerSet{}, err + } + + var stickers StickerSet + err = json.Unmarshal(resp.Result, &stickers) + + return stickers, err +} + +// 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 Poll{}, err + } + + var poll Poll + err = json.Unmarshal(resp.Result, &poll) + + return poll, err +} + +// GetMyCommands gets the currently registered commands. +func (bot *BotAPI) GetMyCommands() ([]BotCommand, error) { + return bot.GetMyCommandsWithConfig(GetMyCommandsConfig{}) +} + +// 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 nil, err + } + + var commands []BotCommand + err = json.Unmarshal(resp.Result, &commands) + + return commands, err +} + +// 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) { + resp, err := bot.Request(config) + if err != nil { + return MessageID{}, err + } + + var messageID MessageID + err = json.Unmarshal(resp.Result, &messageID) + + return messageID, err +} + +// AnswerWebAppQuery sets the result of an interaction with a Web App and send a +// corresponding message on behalf of the user to the chat from which the query originated. +func (bot *BotAPI) AnswerWebAppQuery(config AnswerWebAppQueryConfig) (SentWebAppMessage, error) { + var sentWebAppMessage SentWebAppMessage + + resp, err := bot.Request(config) + if err != nil { + return sentWebAppMessage, err + } + + err = json.Unmarshal(resp.Result, &sentWebAppMessage) + return sentWebAppMessage, err +} + +// GetMyDefaultAdministratorRights gets the current default administrator rights of the bot. +func (bot *BotAPI) GetMyDefaultAdministratorRights(config GetMyDefaultAdministratorRightsConfig) (ChatAdministratorRights, error) { + var rights ChatAdministratorRights + + resp, err := bot.Request(config) + if err != nil { + return rights, err + } + + err = json.Unmarshal(resp.Result, &rights) + return rights, err +} + +// EscapeText takes an input text and escape Telegram markup symbols. +// In this way we can send a text without being afraid of having to escape the characters manually. +// Note that you don't have to include the formatting style in the input text, or it will be escaped too. +// If there is an error, an empty string will be returned. +// +// parseMode is the text formatting mode (ModeMarkdown, ModeMarkdownV2 or ModeHTML) +// text is the input string that will be escaped +func EscapeText(parseMode string, text string) string { + var replacer *strings.Replacer + + if parseMode == ModeHTML { + replacer = strings.NewReplacer("<", "<", ">", ">", "&", "&") + } else if parseMode == ModeMarkdown { + replacer = strings.NewReplacer("_", "\\_", "*", "\\*", "`", "\\`", "[", "\\[") + } else if parseMode == ModeMarkdownV2 { + replacer = strings.NewReplacer( + "_", "\\_", "*", "\\*", "[", "\\[", "]", "\\]", "(", + "\\(", ")", "\\)", "~", "\\~", "`", "\\`", ">", "\\>", + "#", "\\#", "+", "\\+", "-", "\\-", "=", "\\=", "|", + "\\|", "{", "\\{", "}", "\\}", ".", "\\.", "!", "\\!", + ) + } else { + return "" + } + + return replacer.Replace(text) +} diff --git a/vendor/github.com/matterbridge/telegram-bot-api/v6/configs.go b/vendor/github.com/matterbridge/telegram-bot-api/v6/configs.go new file mode 100644 index 00000000..09c9c468 --- /dev/null +++ b/vendor/github.com/matterbridge/telegram-bot-api/v6/configs.go @@ -0,0 +1,2934 @@ +package tgbotapi + +import ( + "bytes" + "fmt" + "io" + "net/url" + "os" + "strconv" +) + +// Telegram constants +const ( + // APIEndpoint is the endpoint for all API methods, + // with formatting for Sprintf. + APIEndpoint = "https://api.telegram.org/bot%s/%s" + // FileEndpoint is the endpoint for downloading a file from Telegram. + FileEndpoint = "https://api.telegram.org/file/bot%s/%s" +) + +// Constant values for ChatActions +const ( + ChatTyping = "typing" + ChatUploadPhoto = "upload_photo" + ChatRecordVideo = "record_video" + ChatUploadVideo = "upload_video" + ChatRecordVoice = "record_voice" + ChatUploadVoice = "upload_voice" + ChatUploadDocument = "upload_document" + ChatChooseSticker = "choose_sticker" + ChatFindLocation = "find_location" + ChatRecordVideoNote = "record_video_note" + ChatUploadVideoNote = "upload_video_note" +) + +// API errors +const ( + // ErrAPIForbidden happens when a token is bad + ErrAPIForbidden = "forbidden" +) + +// Constant values for ParseMode in MessageConfig +const ( + ModeMarkdown = "Markdown" + ModeMarkdownV2 = "MarkdownV2" + ModeHTML = "HTML" +) + +// Constant values for update types +const ( + // UpdateTypeMessage is new incoming message of any kind — text, photo, sticker, etc. + UpdateTypeMessage = "message" + + // UpdateTypeEditedMessage is new version of a message that is known to the bot and was edited + UpdateTypeEditedMessage = "edited_message" + + // UpdateTypeChannelPost is new incoming channel post of any kind — text, photo, sticker, etc. + UpdateTypeChannelPost = "channel_post" + + // UpdateTypeEditedChannelPost is new version of a channel post that is known to the bot and was edited + UpdateTypeEditedChannelPost = "edited_channel_post" + + // UpdateTypeInlineQuery is new incoming inline query + UpdateTypeInlineQuery = "inline_query" + + // UpdateTypeChosenInlineResult i 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" + + // UpdateTypeCallbackQuery is new incoming callback query + UpdateTypeCallbackQuery = "callback_query" + + // UpdateTypeShippingQuery is new incoming shipping query. Only for invoices with flexible price + UpdateTypeShippingQuery = "shipping_query" + + // UpdateTypePreCheckoutQuery is new incoming pre-checkout query. Contains full information about checkout + UpdateTypePreCheckoutQuery = "pre_checkout_query" + + // UpdateTypePoll is new poll state. Bots receive only updates about stopped polls and polls + // which are sent by the bot + UpdateTypePoll = "poll" + + // UpdateTypePollAnswer is when 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" + + // UpdateTypeMyChatMember is when 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" + + // UpdateTypeChatMember is when 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 ( + ErrBadURL = "bad or empty url" +) + +// Chattable is any config type that can be sent. +type Chattable interface { + params() (Params, error) + method() string +} + +// Fileable is any config type that can be sent that includes a file. +type Fileable interface { + Chattable + files() []RequestFile +} + +// 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 +} + +// RequestFileData represents the data to be used for a file. +type RequestFileData interface { + // NeedsUpload shows if the file needs to be uploaded. + NeedsUpload() bool + + // UploadData gets 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) + // SendData gets 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 +} + +// FileBytes contains information about a set of bytes to upload +// as a File. +type FileBytes struct { + Name string + Bytes []byte +} + +func (fb FileBytes) NeedsUpload() bool { + return true +} + +func (fb FileBytes) UploadData() (string, io.Reader, error) { + return fb.Name, bytes.NewReader(fb.Bytes), nil +} + +func (fb FileBytes) SendData() string { + panic("FileBytes must be uploaded") +} + +// 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 +} + +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 + } + + name := fileHandle.Name() + return name, fileHandle, err +} + +func (fp FilePath) SendData() string { + panic("FilePath must be uploaded") +} + +// FileURL is a URL to use as a file for a request. +type FileURL string + +func (fu FileURL) NeedsUpload() bool { + return false +} + +func (fu FileURL) UploadData() (string, io.Reader, error) { + panic("FileURL cannot be uploaded") +} + +func (fu FileURL) SendData() string { + return string(fu) +} + +// FileID is an ID of a file already uploaded to Telegram. +type FileID string + +func (fi FileID) NeedsUpload() bool { + return false +} + +func (fi FileID) UploadData() (string, io.Reader, error) { + panic("FileID cannot be uploaded") +} + +func (fi FileID) SendData() string { + return string(fi) +} + +// fileAttach is an internal file type used for processed media groups. +type fileAttach string + +func (fa fileAttach) NeedsUpload() bool { + return false +} + +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 + MessageThreadID int + ChannelUsername string + ProtectContent bool + 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("message_thread_id", chat.MessageThreadID) + params.AddNonZero("reply_to_message_id", chat.ReplyToMessageID) + params.AddBool("disable_notification", chat.DisableNotification) + params.AddBool("allow_sending_without_reply", chat.AllowSendingWithoutReply) + params.AddBool("protect_content", chat.ProtectContent) + + err := params.AddInterface("reply_markup", chat.ReplyMarkup) + + return params, err +} + +// BaseFile is a base type for all file config types. +type BaseFile struct { + BaseChat + File RequestFileData +} + +func (file BaseFile) params() (Params, error) { + return file.BaseChat.params() +} + +// BaseEdit is base type of all chat edits. +type BaseEdit struct { + ChatID int64 + ChannelUsername string + MessageID int + InlineMessageID string + ReplyMarkup *InlineKeyboardMarkup +} + +func (edit BaseEdit) params() (Params, error) { + params := make(Params) + + if edit.InlineMessageID != "" { + params["inline_message_id"] = edit.InlineMessageID + } else { + params.AddFirstValid("chat_id", edit.ChatID, edit.ChannelUsername) + params.AddNonZero("message_id", edit.MessageID) + } + + err := params.AddInterface("reply_markup", edit.ReplyMarkup) + + return params, err +} + +// BaseSpoiler is base type of structures with spoilers. +type BaseSpoiler struct { + HasSpoiler bool +} + +func (spoiler BaseSpoiler) params() (Params, error) { + params := make(Params) + + if spoiler.HasSpoiler { + params.AddBool("has_spoiler", true) + } + + return params, nil +} + +// MessageConfig contains information about a SendMessage request. +type MessageConfig struct { + BaseChat + Text string + ParseMode string + Entities []MessageEntity + DisableWebPagePreview bool +} + +func (config MessageConfig) params() (Params, error) { + params, err := config.BaseChat.params() + if err != nil { + return params, err + } + + 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 +} + +func (config MessageConfig) method() string { + return "sendMessage" +} + +// ForwardConfig contains information about a ForwardMessage request. +type ForwardConfig struct { + BaseChat + FromChatID int64 // required + FromChannelUsername string + MessageID int // required +} + +func (config ForwardConfig) params() (Params, error) { + params, err := config.BaseChat.params() + if err != nil { + return params, err + } + + params.AddNonZero64("from_chat_id", config.FromChatID) + params.AddNonZero("message_id", config.MessageID) + + return params, nil +} + +func (config ForwardConfig) method() string { + return "forwardMessage" +} + +// CopyMessageConfig contains information about a copyMessage request. +type CopyMessageConfig struct { + BaseChat + FromChatID int64 + FromChannelUsername string + MessageID int + Caption string + ParseMode string + CaptionEntities []MessageEntity +} + +func (config CopyMessageConfig) params() (Params, error) { + params, err := config.BaseChat.params() + if err != nil { + return params, err + } + + 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) + + return params, err +} + +func (config CopyMessageConfig) method() string { + return "copyMessage" +} + +// PhotoConfig contains information about a SendPhoto request. +type PhotoConfig struct { + BaseFile + BaseSpoiler + Thumb RequestFileData + Caption string + ParseMode string + CaptionEntities []MessageEntity +} + +func (config PhotoConfig) params() (Params, error) { + params, err := config.BaseFile.params() + if err != nil { + return params, err + } + + params.AddNonEmpty("caption", config.Caption) + params.AddNonEmpty("parse_mode", config.ParseMode) + err = params.AddInterface("caption_entities", config.CaptionEntities) + if err != nil { + return params, err + } + + p1, err := config.BaseSpoiler.params() + if err != nil { + return params, err + } + params.Merge(p1) + + return params, err +} + +func (config PhotoConfig) method() string { + return "sendPhoto" +} + +func (config PhotoConfig) files() []RequestFile { + files := []RequestFile{{ + Name: "photo", + Data: config.File, + }} + + if config.Thumb != nil { + files = append(files, RequestFile{ + Name: "thumb", + Data: config.Thumb, + }) + } + + return files +} + +// 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 +} + +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 +} + +func (config AudioConfig) method() string { + return "sendAudio" +} + +func (config AudioConfig) files() []RequestFile { + files := []RequestFile{{ + Name: "audio", + Data: config.File, + }} + + if config.Thumb != nil { + files = append(files, RequestFile{ + Name: "thumb", + Data: config.Thumb, + }) + } + + return files +} + +// DocumentConfig contains information about a SendDocument request. +type DocumentConfig struct { + BaseFile + Thumb RequestFileData + Caption string + ParseMode string + CaptionEntities []MessageEntity + DisableContentTypeDetection bool +} + +func (config DocumentConfig) params() (Params, error) { + params, err := config.BaseFile.params() + + params.AddNonEmpty("caption", config.Caption) + params.AddNonEmpty("parse_mode", config.ParseMode) + params.AddBool("disable_content_type_detection", config.DisableContentTypeDetection) + + return params, err +} + +func (config DocumentConfig) method() string { + return "sendDocument" +} + +func (config DocumentConfig) files() []RequestFile { + files := []RequestFile{{ + Name: "document", + Data: config.File, + }} + + if config.Thumb != nil { + files = append(files, RequestFile{ + Name: "thumb", + Data: config.Thumb, + }) + } + + return files +} + +// StickerConfig contains information about a SendSticker request. +type StickerConfig struct { + BaseFile +} + +func (config StickerConfig) params() (Params, error) { + return config.BaseChat.params() +} + +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 + BaseSpoiler + Thumb RequestFileData + Duration int + Caption string + ParseMode string + CaptionEntities []MessageEntity + SupportsStreaming bool +} + +func (config VideoConfig) params() (Params, error) { + params, err := config.BaseChat.params() + if err != nil { + return params, err + } + + 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) + if err != nil { + return params, err + } + + p1, err := config.BaseSpoiler.params() + if err != nil { + return params, err + } + params.Merge(p1) + + return params, err +} + +func (config VideoConfig) method() string { + return "sendVideo" +} + +func (config VideoConfig) files() []RequestFile { + files := []RequestFile{{ + Name: "video", + Data: config.File, + }} + + 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 + BaseSpoiler + Duration int + Thumb RequestFileData + Caption string + ParseMode string + CaptionEntities []MessageEntity +} + +func (config AnimationConfig) params() (Params, error) { + params, err := config.BaseChat.params() + if err != nil { + return params, err + } + + params.AddNonZero("duration", config.Duration) + params.AddNonEmpty("caption", config.Caption) + params.AddNonEmpty("parse_mode", config.ParseMode) + err = params.AddInterface("caption_entities", config.CaptionEntities) + if err != nil { + return params, err + } + + p1, err := config.BaseSpoiler.params() + if err != nil { + return params, err + } + params.Merge(p1) + + return params, err +} + +func (config AnimationConfig) method() string { + return "sendAnimation" +} + +func (config AnimationConfig) files() []RequestFile { + files := []RequestFile{{ + Name: "animation", + Data: config.File, + }} + + 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 +} + +func (config VideoNoteConfig) params() (Params, error) { + params, err := config.BaseChat.params() + + params.AddNonZero("duration", config.Duration) + params.AddNonZero("length", config.Length) + + return params, err +} + +func (config VideoNoteConfig) method() string { + return "sendVideoNote" +} + +func (config VideoNoteConfig) files() []RequestFile { + files := []RequestFile{{ + Name: "video_note", + Data: config.File, + }} + + if config.Thumb != nil { + files = append(files, RequestFile{ + Name: "thumb", + Data: config.Thumb, + }) + } + + return files +} + +// VoiceConfig contains information about a SendVoice request. +type VoiceConfig struct { + BaseFile + Thumb RequestFileData + Caption string + ParseMode string + CaptionEntities []MessageEntity + Duration int +} + +func (config VoiceConfig) params() (Params, error) { + params, err := config.BaseChat.params() + if err != nil { + return params, err + } + + params.AddNonZero("duration", config.Duration) + params.AddNonEmpty("caption", config.Caption) + params.AddNonEmpty("parse_mode", config.ParseMode) + err = params.AddInterface("caption_entities", config.CaptionEntities) + + return params, err +} + +func (config VoiceConfig) method() string { + return "sendVoice" +} + +func (config VoiceConfig) files() []RequestFile { + files := []RequestFile{{ + Name: "voice", + Data: config.File, + }} + + if config.Thumb != nil { + files = append(files, RequestFile{ + Name: "thumb", + Data: config.Thumb, + }) + } + + return files +} + +// LocationConfig contains information about a SendLocation request. +type LocationConfig struct { + BaseChat + Latitude float64 // required + Longitude float64 // required + HorizontalAccuracy float64 // optional + LivePeriod int // optional + Heading int // optional + ProximityAlertRadius int // optional +} + +func (config LocationConfig) params() (Params, error) { + params, err := config.BaseChat.params() + + 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) + + return params, err +} + +func (config LocationConfig) method() string { + return "sendLocation" +} + +// 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 +} + +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 +} + +func (config EditMessageLiveLocationConfig) method() string { + return "editMessageLiveLocation" +} + +// StopMessageLiveLocationConfig stops updating a live location. +type StopMessageLiveLocationConfig struct { + BaseEdit +} + +func (config StopMessageLiveLocationConfig) params() (Params, error) { + return config.BaseEdit.params() +} + +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 + FoursquareType string + GooglePlaceID string + GooglePlaceType string +} + +func (config VenueConfig) params() (Params, error) { + params, err := config.BaseChat.params() + + 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 params, err +} + +func (config VenueConfig) method() string { + return "sendVenue" +} + +// ContactConfig allows you to send a contact. +type ContactConfig struct { + BaseChat + PhoneNumber string + FirstName string + LastName string + VCard string +} + +func (config ContactConfig) params() (Params, error) { + params, err := config.BaseChat.params() + + params["phone_number"] = config.PhoneNumber + params["first_name"] = config.FirstName + + params.AddNonEmpty("last_name", config.LastName) + params.AddNonEmpty("vcard", config.VCard) + + return params, err +} + +func (config ContactConfig) method() string { + return "sendContact" +} + +// SendPollConfig allows you to send a poll. +type SendPollConfig struct { + BaseChat + Question string + Options []string + IsAnonymous bool + Type string + AllowsMultipleAnswers bool + CorrectOptionID int64 + Explanation string + ExplanationParseMode string + ExplanationEntities []MessageEntity + OpenPeriod int + CloseDate int + IsClosed bool +} + +func (config SendPollConfig) params() (Params, error) { + params, err := config.BaseChat.params() + if err != nil { + return params, err + } + + params["question"] = config.Question + 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) + params["correct_option_id"] = strconv.FormatInt(config.CorrectOptionID, 10) + params.AddBool("is_closed", config.IsClosed) + params.AddNonEmpty("explanation", config.Explanation) + 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, err +} + +func (SendPollConfig) method() string { + return "sendPoll" +} + +// GameConfig allows you to send a game. +type GameConfig struct { + BaseChat + GameShortName string +} + +func (config GameConfig) params() (Params, error) { + params, err := config.BaseChat.params() + + params["game_short_name"] = config.GameShortName + + return params, err +} + +func (config GameConfig) method() string { + return "sendGame" +} + +// SetGameScoreConfig allows you to update the game score in a chat. +type SetGameScoreConfig struct { + UserID int64 + Score int + Force bool + DisableEditMessage bool + ChatID int64 + ChannelUsername string + MessageID int + InlineMessageID string +} + +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) + + if config.InlineMessageID != "" { + params["inline_message_id"] = config.InlineMessageID + } else { + params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername) + params.AddNonZero("message_id", config.MessageID) + } + + return params, nil +} + +func (config SetGameScoreConfig) method() string { + return "setGameScore" +} + +// GetGameHighScoresConfig allows you to fetch the high scores for a game. +type GetGameHighScoresConfig struct { + UserID int64 + ChatID int64 + ChannelUsername string + MessageID int + InlineMessageID string +} + +func (config GetGameHighScoresConfig) params() (Params, error) { + params := make(Params) + + params.AddNonZero64("user_id", config.UserID) + + if config.InlineMessageID != "" { + params["inline_message_id"] = config.InlineMessageID + } else { + params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername) + params.AddNonZero("message_id", config.MessageID) + } + + return params, nil +} + +func (config GetGameHighScoresConfig) method() string { + return "getGameHighScores" +} + +// ChatActionConfig contains information about a SendChatAction request. +type ChatActionConfig struct { + BaseChat + MessageThreadID int + Action string // required +} + +func (config ChatActionConfig) params() (Params, error) { + params, err := config.BaseChat.params() + + params["action"] = config.Action + params.AddNonZero("message_thread_id", config.MessageThreadID) + + return params, err +} + +func (config ChatActionConfig) method() string { + return "sendChatAction" +} + +// EditMessageTextConfig allows you to modify the text in a message. +type EditMessageTextConfig struct { + BaseEdit + Text string + ParseMode string + Entities []MessageEntity + DisableWebPagePreview bool +} + +func (config EditMessageTextConfig) params() (Params, error) { + params, err := config.BaseEdit.params() + if err != nil { + return params, err + } + + 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 params, err +} + +func (config EditMessageTextConfig) method() string { + return "editMessageText" +} + +// EditMessageCaptionConfig allows you to modify the caption of a message. +type EditMessageCaptionConfig struct { + BaseEdit + Caption string + ParseMode string + CaptionEntities []MessageEntity +} + +func (config EditMessageCaptionConfig) params() (Params, error) { + params, err := config.BaseEdit.params() + if err != nil { + return params, err + } + + 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) 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 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 + 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 RequestFileData + IPAddress string + MaxConnections int + AllowedUpdates []string + DropPendingUpdates bool + SecretToken string +} + +func (config WebhookConfig) method() string { + return "setWebhook" +} + +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) + params.AddNonEmpty("secret_token", config.SecretToken) + + 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. +type InlineConfig struct { + InlineQueryID string `json:"inline_query_id"` + Results []interface{} `json:"results"` + CacheTime int `json:"cache_time"` + IsPersonal bool `json:"is_personal"` + NextOffset string `json:"next_offset"` + SwitchPMText string `json:"switch_pm_text"` + 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 +} + +// AnswerWebAppQueryConfig is used to set the result of an interaction with a +// Web App and send a corresponding message on behalf of the user to the chat +// from which the query originated. +type AnswerWebAppQueryConfig struct { + // WebAppQueryID is the unique identifier for the query to be answered. + WebAppQueryID string `json:"web_app_query_id"` + // Result is an InlineQueryResult object describing the message to be sent. + Result interface{} `json:"result"` +} + +func (config AnswerWebAppQueryConfig) method() string { + return "answerWebAppQuery" +} + +func (config AnswerWebAppQueryConfig) params() (Params, error) { + params := make(Params) + + params["web_app_query_id"] = config.WebAppQueryID + err := params.AddInterface("result", config.Result) + + return params, err +} + +// CallbackConfig contains information on making a CallbackQuery response. +type CallbackConfig struct { + CallbackQueryID string `json:"callback_query_id"` + Text string `json:"text"` + ShowAlert bool `json:"show_alert"` + URL string `json:"url"` + 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 int64 +} + +// UnbanChatMemberConfig allows you to unban a user. +type UnbanChatMemberConfig struct { + ChatMemberConfig + OnlyIfBanned bool +} + +func (config UnbanChatMemberConfig) method() string { + return "unbanChatMember" +} + +func (config UnbanChatMemberConfig) params() (Params, error) { + params := make(Params) + + params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername, config.ChannelUsername) + params.AddNonZero64("user_id", config.UserID) + params.AddBool("only_if_banned", config.OnlyIfBanned) + + return params, nil +} + +// BanChatMemberConfig contains extra fields to kick user. +type BanChatMemberConfig struct { + ChatMemberConfig + UntilDate int64 + RevokeMessages bool +} + +func (config BanChatMemberConfig) method() string { + return "banChatMember" +} + +func (config BanChatMemberConfig) params() (Params, error) { + params := make(Params) + + 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 +} + +// KickChatMemberConfig contains extra fields to ban user. +// +// This was renamed to BanChatMember in later versions of the Telegram Bot API. +type KickChatMemberConfig = BanChatMemberConfig + +// RestrictChatMemberConfig contains fields to restrict members of chat +type RestrictChatMemberConfig struct { + ChatMemberConfig + UntilDate int64 + UseIndependentChatPermissions bool + Permissions *ChatPermissions +} + +func (config RestrictChatMemberConfig) method() string { + return "restrictChatMember" +} + +func (config RestrictChatMemberConfig) params() (Params, error) { + params := make(Params) + + params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername, config.ChannelUsername) + params.AddNonZero64("user_id", config.UserID) + params.AddBool("use_independent_chat_permissions", config.UseIndependentChatPermissions) + + err := params.AddInterface("permissions", config.Permissions) + params.AddNonZero64("until_date", config.UntilDate) + + return params, err +} + +// PromoteChatMemberConfig contains fields to promote members of chat +type PromoteChatMemberConfig struct { + ChatMemberConfig + IsAnonymous bool + CanManageChat bool + CanChangeInfo bool + CanPostMessages bool + CanEditMessages bool + CanDeleteMessages bool + CanManageVideoChats bool + CanInviteUsers bool + CanRestrictMembers bool + CanPinMessages bool + CanPromoteMembers bool + CanManageTopics bool +} + +func (config PromoteChatMemberConfig) method() string { + return "promoteChatMember" +} + +func (config PromoteChatMemberConfig) params() (Params, error) { + params := make(Params) + + 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_video_chats", config.CanManageVideoChats) + 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) + params.AddBool("can_manage_topics", config.CanManageTopics) + + return params, nil +} + +// SetChatAdministratorCustomTitle sets the title of an administrative user +// promoted by the bot for a chat. +type SetChatAdministratorCustomTitle struct { + ChatMemberConfig + CustomTitle string +} + +func (SetChatAdministratorCustomTitle) method() string { + return "setChatAdministratorCustomTitle" +} + +func (config SetChatAdministratorCustomTitle) params() (Params, error) { + params := make(Params) + + params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername, config.ChannelUsername) + params.AddNonZero64("user_id", config.UserID) + params.AddNonEmpty("custom_title", config.CustomTitle) + + return params, nil +} + +// BanChatSenderChatConfig bans a channel chat in a supergroup or a channel. The +// owner of the chat will not be able to send messages and join live streams on +// behalf of the chat, unless it is unbanned first. The bot must be an +// administrator in the supergroup or channel for this to work and must have the +// appropriate administrator rights. +type BanChatSenderChatConfig struct { + ChatID int64 + ChannelUsername string + SenderChatID int64 + UntilDate int +} + +func (config BanChatSenderChatConfig) method() string { + return "banChatSenderChat" +} + +func (config BanChatSenderChatConfig) params() (Params, error) { + params := make(Params) + + _ = params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername) + params.AddNonZero64("sender_chat_id", config.SenderChatID) + params.AddNonZero("until_date", config.UntilDate) + + return params, nil +} + +// UnbanChatSenderChatConfig unbans a previously banned channel chat in a +// supergroup or channel. The bot must be an administrator for this to work and +// must have the appropriate administrator rights. +type UnbanChatSenderChatConfig struct { + ChatID int64 + ChannelUsername string + SenderChatID int64 +} + +func (config UnbanChatSenderChatConfig) method() string { + return "unbanChatSenderChat" +} + +func (config UnbanChatSenderChatConfig) params() (Params, error) { + params := make(Params) + + _ = params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername) + params.AddNonZero64("sender_chat_id", config.SenderChatID) + + return params, nil +} + +// ChatConfig contains information about getting information on a chat. +type ChatConfig struct { + ChatID int64 + SuperGroupUsername string +} + +func (config ChatConfig) params() (Params, error) { + params := make(Params) + + params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername) + + return params, nil +} + +// ChatInfoConfig contains information about getting chat information. +type ChatInfoConfig struct { + ChatConfig +} + +func (ChatInfoConfig) method() string { + return "getChat" +} + +// ChatMemberCountConfig contains information about getting the number of users in a chat. +type ChatMemberCountConfig struct { + ChatConfig +} + +func (ChatMemberCountConfig) method() string { + return "getChatMembersCount" +} + +// ChatAdministratorsConfig contains information about getting chat administrators. +type ChatAdministratorsConfig struct { + ChatConfig +} + +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 + UseIndependentChatPermissions bool + 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) + params.AddBool("use_independent_chat_permissions", config.UseIndependentChatPermissions) + 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" +} + +// InvoiceLinkConfig contains information for createInvoiceLink method +type InvoiceLinkConfig struct { + Title string //Required + Description string //Required + Payload string //Required + ProviderToken string //Required + Currency string //Required + Prices []LabeledPrice //Required + MaxTipAmount int + SuggestedTipAmounts []int + 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 InvoiceLinkConfig) params() (Params, error) { + params := make(Params) + + 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("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("send_phone_number_to_provider", config.SendPhoneNumberToProvider) + params.AddBool("send_email_to_provider", config.SendEmailToProvider) + params.AddBool("is_flexible", config.IsFlexible) + + return params, err +} + +func (config InvoiceLinkConfig) method() string { + return "createInvoiceLink" +} + +// 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 contains 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 +} + +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 +} + +func (config SetChatPhotoConfig) method() string { + return "setChatPhoto" +} + +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 + ChannelUsername string +} + +func (config DeleteChatPhotoConfig) method() string { + return "deleteChatPhoto" +} + +func (config DeleteChatPhotoConfig) params() (Params, error) { + params := make(Params) + + params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername) + + return params, nil +} + +// SetChatTitleConfig allows you to set the title of something other than a private chat. +type SetChatTitleConfig struct { + ChatID int64 + ChannelUsername string + + Title string +} + +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 +} + +func (config GetStickerSetConfig) method() string { + return "getStickerSet" +} + +func (config GetStickerSetConfig) params() (Params, error) { + params := make(Params) + + params["name"] = config.Name + + return params, nil +} + +// GetCustomEmojiStickersConfig get information about +// custom emoji stickers by their identifiers. +type GetCustomEmojiStickersConfig struct { + CustomEmojiIDs []string +} + +func (config GetCustomEmojiStickersConfig) params() (Params, error) { + params := make(Params) + + params.AddInterface("custom_emoji_ids", config.CustomEmojiIDs) + + return params, nil +} + +func (config GetCustomEmojiStickersConfig) method() string { + return "getCustomEmojiStickers" +} + +// 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 + StickerType string + Emojis string + ContainsMasks bool // deprecated + 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) + params.AddNonEmpty("sticker_type", string(config.StickerType)) + 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 +} + +// GetForumTopicIconStickersConfig allows you to get custom emoji stickers, +// which can be used as a forum topic icon by any user. +type GetForumTopicIconStickersConfig struct{} + +func (config GetForumTopicIconStickersConfig) method() string { + return "getForumTopicIconStickers" +} + +func (config GetForumTopicIconStickersConfig) params() (Params, error) { + return nil, nil +} + +// BaseForum is a base type for all forum config types. +type BaseForum struct { + ChatID int64 + SuperGroupUsername string +} + +func (config BaseForum) params() (Params, error) { + params := make(Params) + + params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername) + + return params, nil +} + +// CreateForumTopicConfig allows you to create a topic +// in a forum supergroup chat. +type CreateForumTopicConfig struct { + BaseForum + Name string + IconColor int + IconCustomEmojiID string +} + +func (config CreateForumTopicConfig) method() string { + return "createForumTopic" +} + +func (config CreateForumTopicConfig) params() (Params, error) { + params := make(Params) + + params.AddNonEmpty("name", config.Name) + params.AddNonZero("icon_color", config.IconColor) + params.AddNonEmpty("icon_custom_emoji_id", config.IconCustomEmojiID) + + p1, _ := config.BaseForum.params() + params.Merge(p1) + + return params, nil +} + +// EditForumTopicConfig allows you to edit +// name and icon of a topic in a forum supergroup chat. +type EditForumTopicConfig struct { + BaseForum + MessageThreadID int + Name string + IconCustomEmojiID string +} + +func (config EditForumTopicConfig) method() string { + return "editForumTopic" +} + +func (config EditForumTopicConfig) params() (Params, error) { + params := make(Params) + + params.AddNonZero("message_thread_id", config.MessageThreadID) + params.AddNonEmpty("name", config.Name) + params.AddNonEmpty("icon_custom_emoji_id", config.IconCustomEmojiID) + + p1, _ := config.BaseForum.params() + params.Merge(p1) + + return params, nil +} + +// CloseForumTopicConfig allows you to close +// an open topic in a forum supergroup chat. +type CloseForumTopicConfig struct { + BaseForum + MessageThreadID int +} + +func (config CloseForumTopicConfig) method() string { + return "closeForumTopic" +} + +func (config CloseForumTopicConfig) params() (Params, error) { + params := make(Params) + + params.AddNonZero("message_thread_id", config.MessageThreadID) + + p1, _ := config.BaseForum.params() + params.Merge(p1) + + return params, nil +} + +// ReopenForumTopicConfig allows you to reopen +// an closed topic in a forum supergroup chat. +type ReopenForumTopicConfig struct { + BaseForum + MessageThreadID int +} + +func (config ReopenForumTopicConfig) method() string { + return "reopenForumTopic" +} + +func (config ReopenForumTopicConfig) params() (Params, error) { + params := make(Params) + + params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername) + params.AddNonZero("message_thread_id", config.MessageThreadID) + + p1, _ := config.BaseForum.params() + params.Merge(p1) + + return params, nil +} + +// DeleteForumTopicConfig allows you to delete a forum topic +// along with all its messages in a forum supergroup chat. +type DeleteForumTopicConfig struct { + BaseForum + MessageThreadID int +} + +func (config DeleteForumTopicConfig) method() string { + return "deleteForumTopic" +} + +func (config DeleteForumTopicConfig) params() (Params, error) { + params := make(Params) + + params.AddNonZero("message_thread_id", config.MessageThreadID) + + p1, _ := config.BaseForum.params() + params.Merge(p1) + + return params, nil +} + +// UnpinAllForumTopicMessagesConfig allows you to clear the list +// of pinned messages in a forum topic. +type UnpinAllForumTopicMessagesConfig struct { + BaseForum + MessageThreadID int +} + +func (config UnpinAllForumTopicMessagesConfig) method() string { + return "unpinAllForumTopicMessages" +} + +func (config UnpinAllForumTopicMessagesConfig) params() (Params, error) { + params := make(Params) + + params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername) + params.AddNonZero("message_thread_id", config.MessageThreadID) + + p1, _ := config.BaseForum.params() + params.Merge(p1) + + return params, nil +} + +// UnpinAllForumTopicMessagesConfig allows you to edit the name of +// the 'General' topic in a forum supergroup chat. +// The bot must be an administrator in the chat for this to work +// and must have can_manage_topics administrator rights. Returns True on success. +type EditGeneralForumTopicConfig struct { + BaseForum + Name string +} + +func (config EditGeneralForumTopicConfig) method() string { + return "editGeneralForumTopic" +} + +func (config EditGeneralForumTopicConfig) params() (Params, error) { + params := make(Params) + + params.AddNonEmpty("name", config.Name) + + p1, _ := config.BaseForum.params() + params.Merge(p1) + + return params, nil +} + +// CloseGeneralForumTopicConfig allows you to to close an open 'General' topic +// in a forum supergroup chat. The bot must be an administrator in the chat +// for this to work and must have the can_manage_topics administrator rights. +// Returns True on success. +type CloseGeneralForumTopicConfig struct{ BaseForum } + +func (config CloseGeneralForumTopicConfig) method() string { + return "closeGeneralForumTopic" +} + +// CloseGeneralForumTopicConfig allows you to reopen a closed 'General' topic +// in a forum supergroup chat. The bot must be an administrator in the chat +// for this to work and must have the can_manage_topics administrator rights. +// The topic will be automatically unhidden if it was hidden. +// Returns True on success. +type ReopenGeneralForumTopicConfig struct{ BaseForum } + +func (config ReopenGeneralForumTopicConfig) method() string { + return "reopenGeneralForumTopic" +} + +// HideGeneralForumTopicConfig allows you to hide the 'General' topic +// in a forum supergroup chat. The bot must be an administrator in the chat +// for this to work and must have the can_manage_topics administrator rights. +// The topic will be automatically closed if it was open. +// Returns True on success. +type HideGeneralForumTopicConfig struct{ BaseForum } + +func (config HideGeneralForumTopicConfig) method() string { + return "hideGeneralForumTopic" +} + +// UnhideGeneralForumTopicConfig allows you to unhide the 'General' topic +// in a forum supergroup chat. The bot must be an administrator in the chat +// for this to work and must have the can_manage_topics administrator rights. +// Returns True on success. +type UnhideGeneralForumTopicConfig struct{ BaseForum } + +func (config UnhideGeneralForumTopicConfig) method() string { + return "unhideGeneralForumTopic" +} + +// MediaGroupConfig allows you to send a group of media. +// +// Media consist of InputMedia items (InputMediaPhoto, InputMediaVideo). +type MediaGroupConfig struct { + BaseChat + Media []interface{} +} + +func (config MediaGroupConfig) method() string { + return "sendMediaGroup" +} + +func (config MediaGroupConfig) params() (Params, error) { + params, err := config.BaseChat.params() + if err != nil { + return nil, err + } + + 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 🎳, values 1-5 for 🏀 and ⚽, + // and values 1-64 for 🎰. + // Defaults to “🎲” + Emoji string +} + +func (config DiceConfig) method() string { + return "sendDice" +} + +func (config DiceConfig) params() (Params, error) { + params, err := config.BaseChat.params() + if err != nil { + return params, err + } + + 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 + } + err := params.AddInterface("scope", config.Scope) + params.AddNonEmpty("language_code", config.LanguageCode) + + return params, err +} + +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 +} + +// SetChatMenuButtonConfig changes the bot's menu button in a private chat, +// or the default menu button. +type SetChatMenuButtonConfig struct { + ChatID int64 + ChannelUsername string + + MenuButton *MenuButton +} + +func (config SetChatMenuButtonConfig) method() string { + return "setChatMenuButton" +} + +func (config SetChatMenuButtonConfig) params() (Params, error) { + params := make(Params) + + if err := params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername); err != nil { + return params, err + } + err := params.AddInterface("menu_button", config.MenuButton) + + return params, err +} + +type GetChatMenuButtonConfig struct { + ChatID int64 + ChannelUsername string +} + +func (config GetChatMenuButtonConfig) method() string { + return "getChatMenuButton" +} + +func (config GetChatMenuButtonConfig) params() (Params, error) { + params := make(Params) + + err := params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername) + + return params, err +} + +type SetMyDefaultAdministratorRightsConfig struct { + Rights ChatAdministratorRights + ForChannels bool +} + +func (config SetMyDefaultAdministratorRightsConfig) method() string { + return "setMyDefaultAdministratorRights" +} + +func (config SetMyDefaultAdministratorRightsConfig) params() (Params, error) { + params := make(Params) + + err := params.AddInterface("rights", config.Rights) + params.AddBool("for_channels", config.ForChannels) + + return params, err +} + +type GetMyDefaultAdministratorRightsConfig struct { + ForChannels bool +} + +func (config GetMyDefaultAdministratorRightsConfig) method() string { + return "getMyDefaultAdministratorRights" +} + +func (config GetMyDefaultAdministratorRightsConfig) params() (Params, error) { + params := make(Params) + + params.AddBool("for_channels", config.ForChannels) + + return params, nil +} + +// 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/vendor/github.com/matterbridge/telegram-bot-api/v6/helpers.go b/vendor/github.com/matterbridge/telegram-bot-api/v6/helpers.go new file mode 100644 index 00000000..bdf2ee97 --- /dev/null +++ b/vendor/github.com/matterbridge/telegram-bot-api/v6/helpers.go @@ -0,0 +1,989 @@ +package tgbotapi + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "net/url" + "sort" + "strings" +) + +// NewMessage creates a new Message. +// +// chatID is where to send it, text is the message text. +func NewMessage(chatID int64, text string) MessageConfig { + return MessageConfig{ + BaseChat: BaseChat{ + ChatID: chatID, + ReplyToMessageID: 0, + }, + Text: text, + DisableWebPagePreview: false, + } +} + +// NewDeleteMessage creates a request to delete a message. +func NewDeleteMessage(chatID int64, messageID int) DeleteMessageConfig { + return DeleteMessageConfig{ + ChatID: chatID, + MessageID: messageID, + } +} + +// NewMessageToChannel creates a new Message that is sent to a channel +// by username. +// +// username is the username of the channel, text is the message text, +// and the username should be in the form of `@username`. +func NewMessageToChannel(username string, text string) MessageConfig { + return MessageConfig{ + BaseChat: BaseChat{ + ChannelUsername: username, + }, + Text: text, + } +} + +// NewForward creates a new forward. +// +// chatID is where to send it, fromChatID is the source chat, +// and messageID is the ID of the original message. +func NewForward(chatID int64, fromChatID int64, messageID int) ForwardConfig { + return ForwardConfig{ + BaseChat: BaseChat{ChatID: chatID}, + FromChatID: fromChatID, + MessageID: messageID, + } +} + +// 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 NewPhoto(chatID int64, file RequestFileData) PhotoConfig { + return PhotoConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + File: file, + }, + } +} + +// NewPhotoToChannel creates a new photo uploader to send a photo to a channel. +// +// Note that you must send animated GIFs as a document. +func NewPhotoToChannel(username string, file RequestFileData) PhotoConfig { + return PhotoConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ + ChannelUsername: username, + }, + File: file, + }, + } +} + +// NewAudio creates a new sendAudio request. +func NewAudio(chatID int64, file RequestFileData) AudioConfig { + return AudioConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + File: file, + }, + } +} + +// NewDocument creates a new sendDocument request. +func NewDocument(chatID int64, file RequestFileData) DocumentConfig { + return DocumentConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + File: file, + }, + } +} + +// NewSticker creates a new sendSticker request. +func NewSticker(chatID int64, file RequestFileData) StickerConfig { + return StickerConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + File: file, + }, + } +} + +// NewVideo creates a new sendVideo request. +func NewVideo(chatID int64, file RequestFileData) VideoConfig { + return VideoConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + File: file, + }, + } +} + +// NewAnimation creates a new sendAnimation request. +func NewAnimation(chatID int64, file RequestFileData) AnimationConfig { + return AnimationConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + File: file, + }, + } +} + +// NewVideoNote creates a new sendVideoNote request. +// +// chatID is where to send it, file is a string path to the file, +// FileReader, or FileBytes. +func NewVideoNote(chatID int64, length int, file RequestFileData) VideoNoteConfig { + return VideoNoteConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + File: file, + }, + Length: length, + } +} + +// NewVoice creates a new sendVoice request. +func NewVoice(chatID int64, file RequestFileData) VoiceConfig { + return VoiceConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + File: file, + }, + } +} + +// 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, + }, + Media: files, + } +} + +// NewInputMediaPhoto creates a new InputMediaPhoto. +func NewInputMediaPhoto(media RequestFileData) InputMediaPhoto { + return InputMediaPhoto{ + BaseInputMedia{ + Type: "photo", + Media: media, + }, + } +} + +// NewInputMediaVideo creates a new InputMediaVideo. +func NewInputMediaVideo(media RequestFileData) InputMediaVideo { + return InputMediaVideo{ + BaseInputMedia: BaseInputMedia{ + Type: "video", + Media: media, + }, + } +} + +// NewInputMediaAnimation creates a new InputMediaAnimation. +func NewInputMediaAnimation(media RequestFileData) InputMediaAnimation { + return InputMediaAnimation{ + BaseInputMedia: BaseInputMedia{ + Type: "animation", + Media: media, + }, + } +} + +// NewInputMediaAudio creates a new InputMediaAudio. +func NewInputMediaAudio(media RequestFileData) InputMediaAudio { + return InputMediaAudio{ + BaseInputMedia: BaseInputMedia{ + Type: "audio", + Media: media, + }, + } +} + +// NewInputMediaDocument creates a new InputMediaDocument. +func NewInputMediaDocument(media RequestFileData) InputMediaDocument { + return InputMediaDocument{ + BaseInputMedia: BaseInputMedia{ + Type: "document", + Media: media, + }, + } +} + +// NewContact allows you to send a shared contact. +func NewContact(chatID int64, phoneNumber, firstName string) ContactConfig { + return ContactConfig{ + BaseChat: BaseChat{ + ChatID: chatID, + }, + PhoneNumber: phoneNumber, + FirstName: firstName, + } +} + +// NewLocation shares your location. +// +// chatID is where to send it, latitude and longitude are coordinates. +func NewLocation(chatID int64, latitude float64, longitude float64) LocationConfig { + return LocationConfig{ + BaseChat: BaseChat{ + ChatID: chatID, + }, + Latitude: latitude, + Longitude: longitude, + } +} + +// NewVenue allows you to send a venue and its location. +func NewVenue(chatID int64, title, address string, latitude, longitude float64) VenueConfig { + return VenueConfig{ + BaseChat: BaseChat{ + ChatID: chatID, + }, + Title: title, + Address: address, + Latitude: latitude, + Longitude: longitude, + } +} + +// NewChatAction sets a chat action. +// Actions last for 5 seconds, or until your next action. +// +// chatID is where to send it, action should be set via Chat constants. +func NewChatAction(chatID int64, action string) ChatActionConfig { + return ChatActionConfig{ + BaseChat: BaseChat{ChatID: chatID}, + Action: action, + } +} + +// NewUserProfilePhotos gets user profile photos. +// +// userID is the ID of the user you wish to get profile photos from. +func NewUserProfilePhotos(userID int64) UserProfilePhotosConfig { + return UserProfilePhotosConfig{ + UserID: userID, + Offset: 0, + Limit: 0, + } +} + +// NewUpdate gets updates since the last Offset. +// +// offset is the last Update ID to include. +// You likely want to set this to the last Update ID plus 1. +func NewUpdate(offset int) UpdateConfig { + return UpdateConfig{ + Offset: offset, + Limit: 0, + Timeout: 0, + } +} + +// NewWebhook creates a new webhook. +// +// link is the url parsable link you wish to get the updates. +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 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. +func NewInlineQueryResultArticle(id, title, messageText string) InlineQueryResultArticle { + return InlineQueryResultArticle{ + Type: "article", + ID: id, + Title: title, + InputMessageContent: InputTextMessageContent{ + Text: messageText, + }, + } +} + +// NewInlineQueryResultArticleMarkdown creates a new inline query article with Markdown parsing. +func NewInlineQueryResultArticleMarkdown(id, title, messageText string) InlineQueryResultArticle { + return InlineQueryResultArticle{ + Type: "article", + ID: id, + Title: title, + InputMessageContent: InputTextMessageContent{ + Text: messageText, + ParseMode: "Markdown", + }, + } +} + +// NewInlineQueryResultArticleMarkdownV2 creates a new inline query article with MarkdownV2 parsing. +func NewInlineQueryResultArticleMarkdownV2(id, title, messageText string) InlineQueryResultArticle { + return InlineQueryResultArticle{ + Type: "article", + ID: id, + Title: title, + InputMessageContent: InputTextMessageContent{ + Text: messageText, + ParseMode: "MarkdownV2", + }, + } +} + +// NewInlineQueryResultArticleHTML creates a new inline query article with HTML parsing. +func NewInlineQueryResultArticleHTML(id, title, messageText string) InlineQueryResultArticle { + return InlineQueryResultArticle{ + Type: "article", + ID: id, + Title: title, + InputMessageContent: InputTextMessageContent{ + Text: messageText, + ParseMode: "HTML", + }, + } +} + +// NewInlineQueryResultGIF creates a new inline query GIF. +func NewInlineQueryResultGIF(id, url string) InlineQueryResultGIF { + return InlineQueryResultGIF{ + Type: "gif", + ID: id, + URL: url, + } +} + +// NewInlineQueryResultCachedGIF create a new inline query with cached photo. +func NewInlineQueryResultCachedGIF(id, gifID string) InlineQueryResultCachedGIF { + return InlineQueryResultCachedGIF{ + Type: "gif", + ID: id, + GIFID: gifID, + } +} + +// NewInlineQueryResultMPEG4GIF creates a new inline query MPEG4 GIF. +func NewInlineQueryResultMPEG4GIF(id, url string) InlineQueryResultMPEG4GIF { + return InlineQueryResultMPEG4GIF{ + Type: "mpeg4_gif", + ID: id, + URL: url, + } +} + +// NewInlineQueryResultCachedMPEG4GIF create a new inline query with cached MPEG4 GIF. +func NewInlineQueryResultCachedMPEG4GIF(id, MPEG4GIFID string) InlineQueryResultCachedMPEG4GIF { + return InlineQueryResultCachedMPEG4GIF{ + Type: "mpeg4_gif", + ID: id, + MPEG4FileID: MPEG4GIFID, + } +} + +// NewInlineQueryResultPhoto creates a new inline query photo. +func NewInlineQueryResultPhoto(id, url string) InlineQueryResultPhoto { + return InlineQueryResultPhoto{ + Type: "photo", + ID: id, + URL: url, + } +} + +// NewInlineQueryResultPhotoWithThumb creates a new inline query photo. +func NewInlineQueryResultPhotoWithThumb(id, url, thumb string) InlineQueryResultPhoto { + return InlineQueryResultPhoto{ + Type: "photo", + ID: id, + URL: url, + ThumbURL: thumb, + } +} + +// NewInlineQueryResultCachedPhoto create a new inline query with cached photo. +func NewInlineQueryResultCachedPhoto(id, photoID string) InlineQueryResultCachedPhoto { + return InlineQueryResultCachedPhoto{ + Type: "photo", + ID: id, + PhotoID: photoID, + } +} + +// NewInlineQueryResultVideo creates a new inline query video. +func NewInlineQueryResultVideo(id, url string) InlineQueryResultVideo { + return InlineQueryResultVideo{ + Type: "video", + ID: id, + URL: url, + } +} + +// NewInlineQueryResultCachedVideo create a new inline query with cached video. +func NewInlineQueryResultCachedVideo(id, videoID, title string) InlineQueryResultCachedVideo { + return InlineQueryResultCachedVideo{ + Type: "video", + ID: id, + VideoID: videoID, + Title: title, + } +} + +// NewInlineQueryResultCachedSticker create a new inline query with cached sticker. +func NewInlineQueryResultCachedSticker(id, stickerID, title string) InlineQueryResultCachedSticker { + return InlineQueryResultCachedSticker{ + Type: "sticker", + ID: id, + StickerID: stickerID, + Title: title, + } +} + +// NewInlineQueryResultAudio creates a new inline query audio. +func NewInlineQueryResultAudio(id, url, title string) InlineQueryResultAudio { + return InlineQueryResultAudio{ + Type: "audio", + ID: id, + URL: url, + Title: title, + } +} + +// NewInlineQueryResultCachedAudio create a new inline query with cached photo. +func NewInlineQueryResultCachedAudio(id, audioID string) InlineQueryResultCachedAudio { + return InlineQueryResultCachedAudio{ + Type: "audio", + ID: id, + AudioID: audioID, + } +} + +// NewInlineQueryResultVoice creates a new inline query voice. +func NewInlineQueryResultVoice(id, url, title string) InlineQueryResultVoice { + return InlineQueryResultVoice{ + Type: "voice", + ID: id, + URL: url, + Title: title, + } +} + +// NewInlineQueryResultCachedVoice create a new inline query with cached photo. +func NewInlineQueryResultCachedVoice(id, voiceID, title string) InlineQueryResultCachedVoice { + return InlineQueryResultCachedVoice{ + Type: "voice", + ID: id, + VoiceID: voiceID, + Title: title, + } +} + +// NewInlineQueryResultDocument creates a new inline query document. +func NewInlineQueryResultDocument(id, url, title, mimeType string) InlineQueryResultDocument { + return InlineQueryResultDocument{ + Type: "document", + ID: id, + URL: url, + Title: title, + MimeType: mimeType, + } +} + +// NewInlineQueryResultCachedDocument create a new inline query with cached photo. +func NewInlineQueryResultCachedDocument(id, documentID, title string) InlineQueryResultCachedDocument { + return InlineQueryResultCachedDocument{ + Type: "document", + ID: id, + DocumentID: documentID, + Title: title, + } +} + +// NewInlineQueryResultLocation creates a new inline query location. +func NewInlineQueryResultLocation(id, title string, latitude, longitude float64) InlineQueryResultLocation { + return InlineQueryResultLocation{ + Type: "location", + ID: id, + Title: title, + Latitude: latitude, + Longitude: longitude, + } +} + +// NewInlineQueryResultVenue creates a new inline query venue. +func NewInlineQueryResultVenue(id, title, address string, latitude, longitude float64) InlineQueryResultVenue { + return InlineQueryResultVenue{ + Type: "venue", + ID: id, + Title: title, + Address: address, + Latitude: latitude, + Longitude: longitude, + } +} + +// NewEditMessageText allows you to edit the text of a message. +func NewEditMessageText(chatID int64, messageID int, text string) EditMessageTextConfig { + return EditMessageTextConfig{ + BaseEdit: BaseEdit{ + ChatID: chatID, + MessageID: messageID, + }, + Text: text, + } +} + +// NewEditMessageTextAndMarkup allows you to edit the text and reply markup of a message. +func NewEditMessageTextAndMarkup(chatID int64, messageID int, text string, replyMarkup InlineKeyboardMarkup) EditMessageTextConfig { + return EditMessageTextConfig{ + BaseEdit: BaseEdit{ + ChatID: chatID, + MessageID: messageID, + ReplyMarkup: &replyMarkup, + }, + Text: text, + } +} + +// NewEditMessageCaption allows you to edit the caption of a message. +func NewEditMessageCaption(chatID int64, messageID int, caption string) EditMessageCaptionConfig { + return EditMessageCaptionConfig{ + BaseEdit: BaseEdit{ + ChatID: chatID, + MessageID: messageID, + }, + Caption: caption, + } +} + +// NewEditMessageReplyMarkup allows you to edit the inline +// keyboard markup. +func NewEditMessageReplyMarkup(chatID int64, messageID int, replyMarkup InlineKeyboardMarkup) EditMessageReplyMarkupConfig { + return EditMessageReplyMarkupConfig{ + BaseEdit: BaseEdit{ + ChatID: chatID, + MessageID: messageID, + ReplyMarkup: &replyMarkup, + }, + } +} + +// NewRemoveKeyboard hides the keyboard, with the option for being selective +// or hiding for everyone. +func NewRemoveKeyboard(selective bool) ReplyKeyboardRemove { + return ReplyKeyboardRemove{ + RemoveKeyboard: true, + Selective: selective, + } +} + +// NewKeyboardButton creates a regular keyboard button. +func NewKeyboardButton(text string) KeyboardButton { + return KeyboardButton{ + Text: text, + } +} + +// NewKeyboardButtonWebApp creates a keyboard button with text +// which goes to a WebApp. +func NewKeyboardButtonWebApp(text string, webapp WebAppInfo) KeyboardButton { + return KeyboardButton{ + Text: text, + WebApp: &webapp, + } +} + +// NewKeyboardButtonContact creates a keyboard button that requests +// user contact information upon click. +func NewKeyboardButtonContact(text string) KeyboardButton { + return KeyboardButton{ + Text: text, + RequestContact: true, + } +} + +// NewKeyboardButtonLocation creates a keyboard button that requests +// user location information upon click. +func NewKeyboardButtonLocation(text string) KeyboardButton { + return KeyboardButton{ + Text: text, + RequestLocation: true, + } +} + +// NewKeyboardButtonRow creates a row of keyboard buttons. +func NewKeyboardButtonRow(buttons ...KeyboardButton) []KeyboardButton { + var row []KeyboardButton + + row = append(row, buttons...) + + return row +} + +// NewReplyKeyboard creates a new regular keyboard with sane defaults. +func NewReplyKeyboard(rows ...[]KeyboardButton) ReplyKeyboardMarkup { + var keyboard [][]KeyboardButton + + keyboard = append(keyboard, rows...) + + return ReplyKeyboardMarkup{ + ResizeKeyboard: true, + Keyboard: keyboard, + } +} + +// NewOneTimeReplyKeyboard creates a new one time keyboard. +func NewOneTimeReplyKeyboard(rows ...[]KeyboardButton) ReplyKeyboardMarkup { + markup := NewReplyKeyboard(rows...) + markup.OneTimeKeyboard = true + return markup +} + +// NewInlineKeyboardButtonData creates an inline keyboard button with text +// and data for a callback. +func NewInlineKeyboardButtonData(text, data string) InlineKeyboardButton { + return InlineKeyboardButton{ + Text: text, + CallbackData: &data, + } +} + +// NewInlineKeyboardButtonWebApp creates an inline keyboard button with text +// which goes to a WebApp. +func NewInlineKeyboardButtonWebApp(text string, webapp WebAppInfo) InlineKeyboardButton { + return InlineKeyboardButton{ + Text: text, + WebApp: &webapp, + } +} + +// 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 { + return InlineKeyboardButton{ + Text: text, + URL: &url, + } +} + +// NewInlineKeyboardButtonSwitch creates an inline keyboard button with +// text which allows the user to switch to a chat or return to a chat. +func NewInlineKeyboardButtonSwitch(text, sw string) InlineKeyboardButton { + return InlineKeyboardButton{ + Text: text, + SwitchInlineQuery: &sw, + } +} + +// NewInlineKeyboardRow creates an inline keyboard row with buttons. +func NewInlineKeyboardRow(buttons ...InlineKeyboardButton) []InlineKeyboardButton { + var row []InlineKeyboardButton + + row = append(row, buttons...) + + return row +} + +// NewInlineKeyboardMarkup creates a new inline keyboard. +func NewInlineKeyboardMarkup(rows ...[]InlineKeyboardButton) InlineKeyboardMarkup { + var keyboard [][]InlineKeyboardButton + + keyboard = append(keyboard, rows...) + + return InlineKeyboardMarkup{ + InlineKeyboard: keyboard, + } +} + +// NewCallback creates a new callback message. +func NewCallback(id, text string) CallbackConfig { + return CallbackConfig{ + CallbackQueryID: id, + Text: text, + ShowAlert: false, + } +} + +// NewCallbackWithAlert creates a new callback message that alerts +// the user. +func NewCallbackWithAlert(id, text string) CallbackConfig { + return CallbackConfig{ + CallbackQueryID: id, + Text: text, + ShowAlert: true, + } +} + +// NewInvoice creates a new Invoice request to the user. +func NewInvoice(chatID int64, title, description, payload, providerToken, startParameter, currency string, prices []LabeledPrice) InvoiceConfig { + return InvoiceConfig{ + BaseChat: BaseChat{ChatID: chatID}, + Title: title, + Description: description, + Payload: payload, + ProviderToken: providerToken, + StartParameter: startParameter, + Currency: currency, + Prices: prices} +} + +// 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: photo, + }, + } +} + +// 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, + }, + } +} + +// 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} +} + +// NewSetMyCommandsWithScope allows you to set the registered commands for a given scope. +func NewSetMyCommandsWithScope(scope BotCommandScope, commands ...BotCommand) SetMyCommandsConfig { + return SetMyCommandsConfig{Commands: commands, Scope: &scope} +} + +// NewSetMyCommandsWithScopeAndLanguage 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{} +} + +// NewDeleteMyCommandsWithScope allows you to delete the registered commands for a given +// scope. +func NewDeleteMyCommandsWithScope(scope BotCommandScope) DeleteMyCommandsConfig { + return DeleteMyCommandsConfig{Scope: &scope} +} + +// NewDeleteMyCommandsWithScopeAndLanguage 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} +} + +// ValidateWebAppData validate data received via the Web App +// https://core.telegram.org/bots/webapps#validating-data-received-via-the-web-app +func ValidateWebAppData(token, telegramInitData string) (bool, error) { + initData, err := url.ParseQuery(telegramInitData) + if err != nil { + return false, fmt.Errorf("error parsing data %w", err) + } + + dataCheckString := make([]string, 0, len(initData)) + for k, v := range initData { + if k == "hash" { + continue + } + if len(v) > 0 { + dataCheckString = append(dataCheckString, fmt.Sprintf("%s=%s", k, v[0])) + } + } + + sort.Strings(dataCheckString) + + secret := hmac.New(sha256.New, []byte("WebAppData")) + secret.Write([]byte(token)) + + hHash := hmac.New(sha256.New, secret.Sum(nil)) + hHash.Write([]byte(strings.Join(dataCheckString, "\n"))) + + hash := hex.EncodeToString(hHash.Sum(nil)) + + if initData.Get("hash") != hash { + return false, errors.New("hash not equal") + } + + return true, nil +} diff --git a/vendor/github.com/matterbridge/telegram-bot-api/v6/log.go b/vendor/github.com/matterbridge/telegram-bot-api/v6/log.go new file mode 100644 index 00000000..18725514 --- /dev/null +++ b/vendor/github.com/matterbridge/telegram-bot-api/v6/log.go @@ -0,0 +1,27 @@ +package tgbotapi + +import ( + "errors" + stdlog "log" + "os" +) + +// BotLogger is an interface that represents the required methods to log data. +// +// Instead of requiring the standard logger, we can just specify the methods we +// use and allow users to pass anything that implements these. +type BotLogger interface { + Println(v ...interface{}) + Printf(format string, v ...interface{}) +} + +var log BotLogger = stdlog.New(os.Stderr, "", stdlog.LstdFlags) + +// SetLogger specifies the logger that the package should use. +func SetLogger(logger BotLogger) error { + if logger == nil { + return errors.New("logger is nil") + } + log = logger + return nil +} diff --git a/vendor/github.com/matterbridge/telegram-bot-api/v6/params.go b/vendor/github.com/matterbridge/telegram-bot-api/v6/params.go new file mode 100644 index 00000000..118af364 --- /dev/null +++ b/vendor/github.com/matterbridge/telegram-bot-api/v6/params.go @@ -0,0 +1,104 @@ +package tgbotapi + +import ( + "encoding/json" + "reflect" + "strconv" +) + +// Params represents a set of parameters that gets passed to a request. +type Params map[string]string + +// AddNonEmpty adds a value if it not an empty string. +func (p Params) AddNonEmpty(key, value string) { + if value != "" { + p[key] = value + } +} + +// AddNonZero adds a value if it is not zero. +func (p Params) AddNonZero(key string, value int) { + if value != 0 { + p[key] = strconv.Itoa(value) + } +} + +// AddNonZero64 is the same as AddNonZero except uses an int64. +func (p Params) AddNonZero64(key string, value int64) { + if value != 0 { + p[key] = strconv.FormatInt(value, 10) + } +} + +// AddBool adds a value of a bool if it is true. +func (p Params) AddBool(key string, value bool) { + if value { + p[key] = strconv.FormatBool(value) + } +} + +// AddNonZeroFloat adds a floating point value that is not zero. +func (p Params) AddNonZeroFloat(key string, value float64) { + if value != 0 { + p[key] = strconv.FormatFloat(value, 'f', 6, 64) + } +} + +// AddInterface adds an interface if it is not nil and can be JSON marshalled. +func (p Params) AddInterface(key string, value interface{}) error { + if value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) { + return nil + } + + b, err := json.Marshal(value) + if err != nil { + return err + } + + p[key] = string(b) + + return nil +} + +// AddFirstValid attempts to add the first item that is not a default value. +// +// For example, AddFirstValid(0, "", "test") would add "test". +func (p Params) AddFirstValid(key string, args ...interface{}) error { + for _, arg := range args { + switch v := arg.(type) { + case int: + if v != 0 { + p[key] = strconv.Itoa(v) + return nil + } + case int64: + if v != 0 { + p[key] = strconv.FormatInt(v, 10) + return nil + } + case string: + if v != "" { + p[key] = v + return nil + } + case nil: + default: + b, err := json.Marshal(arg) + if err != nil { + return err + } + + p[key] = string(b) + return nil + } + } + + return nil +} + +// Merge merges two sets of parameters. Overwrites old fields if present +func (p *Params) Merge(p1 Params) { + for k, v := range p1 { + (*p)[k] = v + } +}
\ No newline at end of file diff --git a/vendor/github.com/matterbridge/telegram-bot-api/v6/passport.go b/vendor/github.com/matterbridge/telegram-bot-api/v6/passport.go new file mode 100644 index 00000000..4fedb965 --- /dev/null +++ b/vendor/github.com/matterbridge/telegram-bot-api/v6/passport.go @@ -0,0 +1,317 @@ +package tgbotapi + +// PassportRequestInfoConfig allows you to request passport info +type PassportRequestInfoConfig struct { + BotID int `json:"bot_id"` + Scope *PassportScope `json:"scope"` + Nonce string `json:"nonce"` + PublicKey string `json:"public_key"` +} + +// PassportScopeElement supports using one or one of several elements. +type PassportScopeElement interface { + ScopeType() string +} + +// PassportScope is the requested scopes of data. +type PassportScope struct { + V int `json:"v"` + Data []PassportScopeElement `json:"data"` +} + +// PassportScopeElementOneOfSeveral allows you to request any one of the +// requested documents. +type PassportScopeElementOneOfSeveral struct { +} + +// ScopeType is the scope type. +func (eo *PassportScopeElementOneOfSeveral) ScopeType() string { + return "one_of" +} + +// PassportScopeElementOne requires the specified element be provided. +type PassportScopeElementOne struct { + Type string `json:"type"` // One of “personal_details”, “passport”, “driver_license”, “identity_card”, “internal_passport”, “address”, “utility_bill”, “bank_statement”, “rental_agreement”, “passport_registration”, “temporary_registration”, “phone_number”, “email” + Selfie bool `json:"selfie"` + Translation bool `json:"translation"` + NativeNames bool `json:"native_name"` +} + +// ScopeType is the scope type. +func (eo *PassportScopeElementOne) ScopeType() string { + return "one" +} + +type ( + // PassportData contains information about Telegram Passport data shared with + // the bot by the user. + PassportData struct { + // Array with information about documents and other Telegram Passport + // elements that was shared with the bot + Data []EncryptedPassportElement `json:"data"` + + // Encrypted credentials required to decrypt the data + Credentials *EncryptedCredentials `json:"credentials"` + } + + // PassportFile represents a file uploaded to Telegram Passport. Currently, all + // Telegram Passport files are in JPEG format when decrypted and don't exceed + // 10MB. + PassportFile struct { + // Unique identifier for this file + FileID string `json:"file_id"` + + FileUniqueID string `json:"file_unique_id"` + + // File size + FileSize int `json:"file_size"` + + // Unix time when the file was uploaded + FileDate int64 `json:"file_date"` + } + + // EncryptedPassportElement contains information about documents or other + // Telegram Passport elements shared with the bot by the user. + EncryptedPassportElement struct { + // Element type. + Type string `json:"type"` + + // Base64-encoded encrypted Telegram Passport element data provided by + // the user, available for "personal_details", "passport", + // "driver_license", "identity_card", "identity_passport" and "address" + // types. Can be decrypted and verified using the accompanying + // EncryptedCredentials. + Data string `json:"data,omitempty"` + + // User's verified phone number, available only for "phone_number" type + PhoneNumber string `json:"phone_number,omitempty"` + + // User's verified email address, available only for "email" type + Email string `json:"email,omitempty"` + + // Array of encrypted files with documents provided by the user, + // available for "utility_bill", "bank_statement", "rental_agreement", + // "passport_registration" and "temporary_registration" types. Files can + // be decrypted and verified using the accompanying EncryptedCredentials. + Files []PassportFile `json:"files,omitempty"` + + // Encrypted file with the front side of the document, provided by the + // user. Available for "passport", "driver_license", "identity_card" and + // "internal_passport". The file can be decrypted and verified using the + // accompanying EncryptedCredentials. + FrontSide *PassportFile `json:"front_side,omitempty"` + + // Encrypted file with the reverse side of the document, provided by the + // user. Available for "driver_license" and "identity_card". The file can + // be decrypted and verified using the accompanying EncryptedCredentials. + ReverseSide *PassportFile `json:"reverse_side,omitempty"` + + // Encrypted file with the selfie of the user holding a document, + // provided by the user; available for "passport", "driver_license", + // "identity_card" and "internal_passport". The file can be decrypted + // and verified using the accompanying EncryptedCredentials. + Selfie *PassportFile `json:"selfie,omitempty"` + } + + // EncryptedCredentials contains data required for decrypting and + // authenticating EncryptedPassportElement. See the Telegram Passport + // Documentation for a complete description of the data decryption and + // authentication processes. + EncryptedCredentials struct { + // Base64-encoded encrypted JSON-serialized data with unique user's + // payload, data hashes and secrets required for EncryptedPassportElement + // decryption and authentication + Data string `json:"data"` + + // Base64-encoded data hash for data authentication + Hash string `json:"hash"` + + // Base64-encoded secret, encrypted with the bot's public RSA key, + // required for data decryption + Secret string `json:"secret"` + } + + // PassportElementError represents an error in the Telegram Passport element + // which was submitted that should be resolved by the user. + PassportElementError interface{} + + // PassportElementErrorDataField represents an issue in one of the data + // fields that was provided by the user. The error is considered resolved + // when the field's value changes. + PassportElementErrorDataField struct { + // Error source, must be data + Source string `json:"source"` + + // The section of the user's Telegram Passport which has the error, one + // of "personal_details", "passport", "driver_license", "identity_card", + // "internal_passport", "address" + Type string `json:"type"` + + // Name of the data field which has the error + FieldName string `json:"field_name"` + + // Base64-encoded data hash + DataHash string `json:"data_hash"` + + // Error message + Message string `json:"message"` + } + + // PassportElementErrorFrontSide represents an issue with the front side of + // a document. The error is considered resolved when the file with the front + // side of the document changes. + PassportElementErrorFrontSide struct { + // Error source, must be front_side + Source string `json:"source"` + + // The section of the user's Telegram Passport which has the issue, one + // of "passport", "driver_license", "identity_card", "internal_passport" + Type string `json:"type"` + + // Base64-encoded hash of the file with the front side of the document + FileHash string `json:"file_hash"` + + // Error message + Message string `json:"message"` + } + + // PassportElementErrorReverseSide represents an issue with the reverse side + // of a document. The error is considered resolved when the file with reverse + // side of the document changes. + PassportElementErrorReverseSide struct { + // Error source, must be reverse_side + Source string `json:"source"` + + // The section of the user's Telegram Passport which has the issue, one + // of "driver_license", "identity_card" + Type string `json:"type"` + + // Base64-encoded hash of the file with the reverse side of the document + FileHash string `json:"file_hash"` + + // Error message + Message string `json:"message"` + } + + // PassportElementErrorSelfie represents an issue with the selfie with a + // document. The error is considered resolved when the file with the selfie + // changes. + PassportElementErrorSelfie struct { + // Error source, must be selfie + Source string `json:"source"` + + // The section of the user's Telegram Passport which has the issue, one + // of "passport", "driver_license", "identity_card", "internal_passport" + Type string `json:"type"` + + // Base64-encoded hash of the file with the selfie + FileHash string `json:"file_hash"` + + // Error message + Message string `json:"message"` + } + + // PassportElementErrorFile represents an issue with a document scan. The + // error is considered resolved when the file with the document scan changes. + PassportElementErrorFile struct { + // Error source, must be a file + Source string `json:"source"` + + // The section of the user's Telegram Passport which has the issue, one + // of "utility_bill", "bank_statement", "rental_agreement", + // "passport_registration", "temporary_registration" + Type string `json:"type"` + + // Base64-encoded file hash + FileHash string `json:"file_hash"` + + // Error message + Message string `json:"message"` + } + + // PassportElementErrorFiles represents an issue with a list of scans. The + // error is considered resolved when the list of files containing the scans + // changes. + PassportElementErrorFiles struct { + // Error source, must be files + Source string `json:"source"` + + // The section of the user's Telegram Passport which has the issue, one + // of "utility_bill", "bank_statement", "rental_agreement", + // "passport_registration", "temporary_registration" + Type string `json:"type"` + + // List of base64-encoded file hashes + FileHashes []string `json:"file_hashes"` + + // Error message + Message string `json:"message"` + } + + // Credentials contains encrypted data. + Credentials struct { + Data SecureData `json:"secure_data"` + // Nonce the same nonce given in the request + Nonce string `json:"nonce"` + } + + // SecureData is a map of the fields and their encrypted values. + SecureData map[string]*SecureValue + // PersonalDetails *SecureValue `json:"personal_details"` + // Passport *SecureValue `json:"passport"` + // InternalPassport *SecureValue `json:"internal_passport"` + // DriverLicense *SecureValue `json:"driver_license"` + // IdentityCard *SecureValue `json:"identity_card"` + // Address *SecureValue `json:"address"` + // UtilityBill *SecureValue `json:"utility_bill"` + // BankStatement *SecureValue `json:"bank_statement"` + // RentalAgreement *SecureValue `json:"rental_agreement"` + // PassportRegistration *SecureValue `json:"passport_registration"` + // TemporaryRegistration *SecureValue `json:"temporary_registration"` + + // SecureValue contains encrypted values for a SecureData item. + SecureValue struct { + Data *DataCredentials `json:"data"` + FrontSide *FileCredentials `json:"front_side"` + ReverseSide *FileCredentials `json:"reverse_side"` + Selfie *FileCredentials `json:"selfie"` + Translation []*FileCredentials `json:"translation"` + Files []*FileCredentials `json:"files"` + } + + // DataCredentials contains information required to decrypt data. + DataCredentials struct { + // DataHash checksum of encrypted data + DataHash string `json:"data_hash"` + // Secret of encrypted data + Secret string `json:"secret"` + } + + // FileCredentials contains information required to decrypt files. + FileCredentials struct { + // FileHash checksum of encrypted data + FileHash string `json:"file_hash"` + // Secret of encrypted data + Secret string `json:"secret"` + } + + // PersonalDetails https://core.telegram.org/passport#personaldetails + PersonalDetails struct { + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + MiddleName string `json:"middle_name"` + BirthDate string `json:"birth_date"` + Gender string `json:"gender"` + CountryCode string `json:"country_code"` + ResidenceCountryCode string `json:"residence_country_code"` + FirstNameNative string `json:"first_name_native"` + LastNameNative string `json:"last_name_native"` + MiddleNameNative string `json:"middle_name_native"` + } + + // IDDocumentData https://core.telegram.org/passport#iddocumentdata + IDDocumentData struct { + DocumentNumber string `json:"document_no"` + ExpiryDate string `json:"expiry_date"` + } +) diff --git a/vendor/github.com/matterbridge/telegram-bot-api/v6/types.go b/vendor/github.com/matterbridge/telegram-bot-api/v6/types.go new file mode 100644 index 00000000..97fce226 --- /dev/null +++ b/vendor/github.com/matterbridge/telegram-bot-api/v6/types.go @@ -0,0 +1,3760 @@ +package tgbotapi + +import ( + "encoding/json" + "errors" + "fmt" + "net/url" + "strings" + "time" +) + +// APIResponse is a response from the Telegram API with the result +// stored raw. +type APIResponse struct { + Ok bool `json:"ok"` + Result json.RawMessage `json:"result,omitempty"` + ErrorCode int `json:"error_code,omitempty"` + Description string `json:"description,omitempty"` + Parameters *ResponseParameters `json:"parameters,omitempty"` +} + +// 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. + // 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. + // If there are no new updates for at least a week, then identifier + // of the next update will be chosen randomly instead of sequentially. + UpdateID int `json:"update_id"` + // Message new incoming message of any kind — text, photo, sticker, etc. + // + // optional + 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,omitempty"` + // ChannelPost new version of a message that is known to the bot and was + // edited + // + // optional + 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,omitempty"` + // InlineQuery new incoming inline query + // + // optional + 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,omitempty"` + // CallbackQuery new incoming callback query + // + // optional + 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,omitempty"` + // 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 + ChatMember *ChatMemberUpdated `json:"chat_member,omitempty"` + // 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 + ChatJoinRequest *ChatJoinRequest `json:"chat_join_request,omitempty"` +} + +// SentFrom returns the user who sent an update. Can be nil, if Telegram did not provide information +// about the user in the update object. +func (u *Update) SentFrom() *User { + switch { + case u.Message != nil: + return u.Message.From + case u.EditedMessage != nil: + return u.EditedMessage.From + case u.InlineQuery != nil: + return u.InlineQuery.From + case u.ChosenInlineResult != nil: + return u.ChosenInlineResult.From + case u.CallbackQuery != nil: + return u.CallbackQuery.From + case u.ShippingQuery != nil: + return u.ShippingQuery.From + case u.PreCheckoutQuery != nil: + return u.PreCheckoutQuery.From + default: + return nil + } +} + +// CallbackData returns the callback query data, if it exists. +func (u *Update) CallbackData() string { + if u.CallbackQuery != nil { + return u.CallbackQuery.Data + } + return "" +} + +// FromChat returns the chat where an update occurred. +func (u *Update) FromChat() *Chat { + switch { + case u.Message != nil: + return u.Message.Chat + case u.EditedMessage != nil: + return u.EditedMessage.Chat + case u.ChannelPost != nil: + return u.ChannelPost.Chat + case u.EditedChannelPost != nil: + return u.EditedChannelPost.Chat + case u.CallbackQuery != nil: + return u.CallbackQuery.Message.Chat + default: + return nil + } +} + +// UpdatesChannel is the channel for getting updates. +type UpdatesChannel <-chan Update + +// Clear discards all unprocessed incoming updates. +func (ch UpdatesChannel) Clear() { + for len(ch) != 0 { + <-ch + } +} + +// User represents a Telegram user or bot. +type User struct { + // ID is a unique identifier for this user or bot + ID int64 `json:"id"` + // IsBot true, if this user is a bot + // + // optional + IsBot bool `json:"is_bot,omitempty"` + // IsPremium true, if user has Telegram Premium + // + // optional + IsPremium bool `json:"is_premium,omitempty"` + // AddedToAttachmentMenu true, if this user added the bot to the attachment menu + // + // optional + AddedToAttachmentMenu bool `json:"added_to_attachment_menu,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,omitempty"` + // UserName user's or bot's username + // + // optional + 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,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 + 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. +// +// It is normally a user's username, but falls back to a first/last +// name as available. +func (u *User) String() string { + if u == nil { + return "" + } + if u.UserName != "" { + return u.UserName + } + + name := u.FirstName + if u.LastName != "" { + name += " " + u.LastName + } + + return name +} + +// Chat represents a chat. +type Chat struct { + // ID is a unique identifier for this chat + ID int64 `json:"id"` + // Type of chat, can be either “private”, “group”, “supergroup” or “channel” + Type string `json:"type"` + // Title for supergroups, channels and group chats + // + // optional + Title string `json:"title,omitempty"` + // UserName for private chats, supergroups and channels if available + // + // optional + UserName string `json:"username,omitempty"` + // FirstName of the other party in a private chat + // + // optional + FirstName string `json:"first_name,omitempty"` + // LastName of the other party in a private chat + // + // optional + LastName string `json:"last_name,omitempty"` + // IsForum is true if the supergroup chat is a forum (has topics enabled) + // + // optional + IsForum bool `json:"is_forum,omitempty"` + // Photo is a chat photo + Photo *ChatPhoto `json:"photo"` + // If non-empty, the list of all active chat usernames; + // for private chats, supergroups and channels. Returned only in getChat. + // + // optional + ActiveUsernames []string `json:"active_usernames,omitempty"` + // Custom emoji identifier of emoji status of the other party + // in a private chat. Returned only in getChat. + // + // optional + EmojiStatusCustomEmojiID string `json:"emoji_status_custom_emoji_id,omitempty"` + // Bio is the bio of the other party in a private chat. Returned only in + // getChat + // + // optional + Bio string `json:"bio,omitempty"` + // HasPrivateForwards is true if privacy settings of the other party in the + // private chat allows to use tg://user?id=<user_id> links only in chats + // with the user. Returned only in getChat. + // + // optional + HasPrivateForwards bool `json:"has_private_forwards,omitempty"` + // HasRestrictedVoiceAndVideoMessages if the privacy settings of the other party + // restrict sending voice and video note messages + // in the private chat. Returned only in getChat. + // + // optional + HasRestrictedVoiceAndVideoMessages bool `json:"has_restricted_voice_and_video_messages,omitempty"` + // JoinToSendMessages is true, if users need to join the supergroup + // before they can send messages. + // Returned only in getChat + // + // optional + JoinToSendMessages bool `json:"join_to_send_messages,omitempty"` + // JoinByRequest is true, if all users directly joining the supergroup + // need to be approved by supergroup administrators. + // Returned only in getChat. + // + // optional + JoinByRequest bool `json:"join_by_request,omitempty"` + // Description for groups, supergroups and channel chats + // + // optional + Description string `json:"description,omitempty"` + // InviteLink is a chat invite link, for groups, supergroups and channel chats. + // Each administrator in a chat generates their own invite links, + // so the bot must first generate the link using exportChatInviteLink + // + // optional + InviteLink string `json:"invite_link,omitempty"` + // PinnedMessage is the pinned message, for groups, supergroups and channels + // + // optional + PinnedMessage *Message `json:"pinned_message,omitempty"` + // Permissions are 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 unprivileged user. Returned only in + // getChat. + // + // optional + SlowModeDelay int `json:"slow_mode_delay,omitempty"` + // MessageAutoDeleteTime is the time after which all messages sent to the + // chat will be automatically deleted; in seconds. Returned only in getChat. + // + // optional + MessageAutoDeleteTime int `json:"message_auto_delete_time,omitempty"` + // HasAggressiveAntiSpamEnabled is true if aggressive anti-spam checks are enabled + // in the supergroup. The field is only available to chat administrators. + // Returned only in getChat. + // + // optional + HasAggressiveAntiSpamEnabled bool `json:"has_aggressive_anti_spam_enabled,omitempty"` + // HasHiddenMembers is true if non-administrators can only get + // the list of bots and administrators in the chat. + // + // optional + HasHiddenMembers bool `json:"has_hidden_members,omitempty"` + // HasProtectedContent is true if messages from the chat can't be forwarded + // to other chats. Returned only in getChat. + // + // optional + HasProtectedContent bool `json:"has_protected_content,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 + Location *ChatLocation `json:"location,omitempty"` +} + +// IsPrivate returns if the Chat is a private conversation. +func (c Chat) IsPrivate() bool { + return c.Type == "private" +} + +// IsGroup returns if the Chat is a group. +func (c Chat) IsGroup() bool { + return c.Type == "group" +} + +// IsSuperGroup returns if the Chat is a supergroup. +func (c Chat) IsSuperGroup() bool { + return c.Type == "supergroup" +} + +// IsChannel returns if the Chat is a channel. +func (c Chat) IsChannel() bool { + return c.Type == "channel" +} + +// ChatConfig returns a ChatConfig struct for chat related methods. +func (c Chat) ChatConfig() ChatConfig { + return ChatConfig{ChatID: c.ID} +} + +// Message represents a message. +type Message struct { + // MessageID is a unique message identifier inside this chat + MessageID int `json:"message_id"` + // Unique identifier of a message thread to which the message belongs; + // for supergroups only + // + // optional + MessageThreadID int `json:"message_thread_id,omitempty"` + // From is a sender, empty for messages sent to channels; + // + // optional + 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 + Chat *Chat `json:"chat"` + // ForwardFrom for forwarded messages, sender of the original message; + // + // optional + ForwardFrom *User `json:"forward_from,omitempty"` + // ForwardFromChat for messages forwarded from channels, + // information about the original channel; + // + // optional + 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,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,omitempty"` + // IsTopicMessage true if the message is sent to a forum topic + // + // optional + IsTopicMessage bool `json:"is_topic_message,omitempty"` + // IsAutomaticForward is true if the message is a channel post that was + // automatically forwarded to the connected discussion group. + // + // optional + IsAutomaticForward bool `json:"is_automatic_forward,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,omitempty"` + // ViaBot through which the message was sent; + // + // optional + ViaBot *User `json:"via_bot,omitempty"` + // EditDate of the message was last edited in Unix time; + // + // optional + EditDate int `json:"edit_date,omitempty"` + // HasProtectedContent is true if the message can't be forwarded. + // + // optional + HasProtectedContent bool `json:"has_protected_content,omitempty"` + // MediaGroupID is the unique identifier of a media message group this message belongs to; + // + // optional + 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,omitempty"` + // Text is for text messages, the actual UTF-8 text of the message, 0-4096 characters; + // + // optional + Text string `json:"text,omitempty"` + // Entities are for text messages, special entities like usernames, + // URLs, bot commands, etc. that appear in the text; + // + // optional + 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 + Animation *Animation `json:"animation,omitempty"` + // PremiumAnimation message is an animation, information about the animation. + // For backward compatibility, when this field is set, the document field will also be set; + // + // optional + PremiumAnimation *Animation `json:"premium_animation,omitempty"` + // Audio message is an audio file, information about the file; + // + // optional + Audio *Audio `json:"audio,omitempty"` + // Document message is a general file, information about the file; + // + // optional + Document *Document `json:"document,omitempty"` + // Photo message is a photo, available sizes of the photo; + // + // optional + Photo []PhotoSize `json:"photo,omitempty"` + // Sticker message is a sticker, information about the sticker; + // + // optional + Sticker *Sticker `json:"sticker,omitempty"` + // Video message is a video, information about the video; + // + // optional + Video *Video `json:"video,omitempty"` + // VideoNote message is a video note, information about the video message; + // + // optional + VideoNote *VideoNote `json:"video_note,omitempty"` + // Voice message is a voice message, information about the file; + // + // optional + Voice *Voice `json:"voice,omitempty"` + // Caption for the animation, audio, document, photo, video or voice, 0-1024 characters; + // + // optional + Caption string `json:"caption,omitempty"` + // CaptionEntities; + // + // optional + CaptionEntities []MessageEntity `json:"caption_entities,omitempty"` + // HasSpoiler True, if the message media is covered by a spoiler animation + // + // optional + HasMediaSpoiler bool `json:"has_media_spoiler,omitempty"` + // Contact message is a shared contact, information about the contact; + // + // optional + 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 + 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; + // + // optional + 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,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,omitempty"` + // NewChatTitle is a chat title was changed to this value; + // + // optional + NewChatTitle string `json:"new_chat_title,omitempty"` + // NewChatPhoto is a chat photo was change to this value; + // + // optional + NewChatPhoto []PhotoSize `json:"new_chat_photo,omitempty"` + // DeleteChatPhoto is a service message: the chat photo was deleted; + // + // optional + DeleteChatPhoto bool `json:"delete_chat_photo,omitempty"` + // GroupChatCreated is a service message: the group has been created; + // + // optional + 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. + // It can only be found in ReplyToMessage if someone replies to a very first message + // in a directly created supergroup; + // + // optional + 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. + // It can only be found in ReplyToMessage + // if someone replies to a very first message in a channel; + // + // optional + 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,omitempty"` + // 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. + // But it is smaller than 52 bits, so a signed 64-bit integer + // or double-precision float type are safe for storing this identifier; + // + // optional + 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. + // But it is smaller than 52 bits, so a signed 64-bit integer + // or double-precision float type are safe for storing this identifier; + // + // optional + 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,omitempty"` + // Invoice message is an invoice for a payment; + // + // optional + 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,omitempty"` + // UserShared is a service message: a user was shared with the bot + // + // optional + UserShared *UserShared `json:"user_shared,omitempty"` + // ChatShared is a service message: a chat was shared with the bot + // + // optional + ChatShared *ChatShared `json:"chat_shared,omitempty"` + // ConnectedWebsite is the domain name of the website on which the user has + // logged in; + // + // optional + ConnectedWebsite string `json:"connected_website,omitempty"` + // WriteAccessAllowed is a service message: the user allowed the bot + // added to the attachment menu to write messages + // + // optional + WriteAccessAllowed *WriteAccessAllowed `json:"write_access_allowed,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,omitempty"` + // ForumTopicCreated is a service message: forum topic created + // + // optional + ForumTopicCreated *ForumTopicCreated `json:"forum_topic_created,omitempty"` + // ForumTopicClosed is a service message: forum topic edited + // + // optional + ForumTopicEdited *ForumTopicEdited `json:"forum_topic_edited,omitempty"` + // ForumTopicClosed is a service message: forum topic closed + // + // optional + ForumTopicClosed *ForumTopicClosed `json:"forum_topic_closed,omitempty"` + // ForumTopicReopened is a service message: forum topic reopened + // + // optional + ForumTopicReopened *ForumTopicReopened `json:"forum_topic_reopened,omitempty"` + // GeneralForumTopicHidden is a service message: the 'General' forum topic hidden + // + // optional + GeneralForumTopicHidden *GeneralForumTopicHidden `json:"general_forum_topic_hidden,omitempty"` + // GeneralForumTopicUnhidden is a service message: the 'General' forum topic unhidden + // + // optional + GeneralForumTopicUnhidden *GeneralForumTopicUnhidden `json:"general_forum_topic_unhidden,omitempty"` + // VideoChatScheduled is a service message: video chat scheduled. + // + // optional + VideoChatScheduled *VideoChatScheduled `json:"video_chat_scheduled,omitempty"` + // VideoChatStarted is a service message: video chat started. + // + // optional + VideoChatStarted *VideoChatStarted `json:"video_chat_started,omitempty"` + // VideoChatEnded is a service message: video chat ended. + // + // optional + VideoChatEnded *VideoChatEnded `json:"video_chat_ended,omitempty"` + // VideoChatParticipantsInvited is a service message: new participants + // invited to a video chat. + // + // optional + VideoChatParticipantsInvited *VideoChatParticipantsInvited `json:"video_chat_participants_invited,omitempty"` + // WebAppData is a service message: data sent by a Web App. + // + // optional + WebAppData *WebAppData `json:"web_app_data,omitempty"` + // 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. +func (m *Message) Time() time.Time { + return time.Unix(int64(m.Date), 0) +} + +// IsCommand returns true if message starts with a "bot_command" entity. +func (m *Message) IsCommand() bool { + if m.Entities == nil || len(m.Entities) == 0 { + return false + } + + entity := m.Entities[0] + return entity.Offset == 0 && entity.IsCommand() +} + +// Command checks if the message was a command and if it was, returns the +// command. If the Message was not a command, it returns an empty string. +// +// If the command contains the at name syntax, it is removed. Use +// CommandWithAt() if you do not want that. +func (m *Message) Command() string { + command := m.CommandWithAt() + + if i := strings.Index(command, "@"); i != -1 { + command = command[:i] + } + + return command +} + +// CommandWithAt checks if the message was a command and if it was, returns the +// command. If the Message was not a command, it returns an empty string. +// +// If the command contains the at name syntax, it is not removed. Use Command() +// if you want that. +func (m *Message) CommandWithAt() string { + if !m.IsCommand() { + return "" + } + + // IsCommand() checks that the message begins with a bot_command entity + entity := m.Entities[0] + return m.Text[1:entity.Length] +} + +// CommandArguments checks if the message was a command and if it was, +// returns all text after the command name. If the Message was not a +// command, it returns an empty string. +// +// Note: The first character after the command name is omitted: +// - "/foo bar baz" yields "bar baz", not " bar baz" +// - "/foo-bar baz" yields "bar baz", too +// Even though the latter is not a command conforming to the spec, the API +// marks "/foo" as command entity. +func (m *Message) CommandArguments() string { + if !m.IsCommand() { + return "" + } + + // IsCommand() checks that the message begins with a bot_command entity + entity := m.Entities[0] + + if len(m.Text) == entity.Length { + return "" // The command makes up the whole message + } + + return m.Text[entity.Length+1:] +} + +// 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: + // “mention” (@username), + // “hashtag” (#hashtag), + // “cashtag” ($USD), + // “bot_command” (/start@jobs_bot), + // “url” (https://telegram.org), + // “email” (do-not-reply@telegram.org), + // “phone_number” (+1-212-555-0123), + // “bold” (bold text), + // “italic” (italic text), + // “underline” (underlined text), + // “strikethrough” (strikethrough text), + // "spoiler" (spoiler message), + // “code” (monowidth string), + // “pre” (monowidth block), + // “text_link” (for clickable text URLs), + // “text_mention” (for users without usernames) + // “text_mention” (for inline custom emoji stickers) + Type string `json:"type"` + // Offset in UTF-16 code units to the start of the entity + Offset int `json:"offset"` + // Length + Length int `json:"length"` + // URL for “text_link” only, url that will be opened after user taps on the text + // + // optional + URL string `json:"url,omitempty"` + // User for “text_mention” only, the mentioned user + // + // optional + User *User `json:"user,omitempty"` + // Language for “pre” only, the programming language of the entity text + // + // optional + Language string `json:"language,omitempty"` + // CustomEmojiID for “custom_emoji” only, unique identifier of the custom emoji + // + // optional + CustomEmojiID string `json:"custom_emoji_id"` +} + +// ParseURL attempts to parse a URL contained within a MessageEntity. +func (e MessageEntity) ParseURL() (*url.URL, error) { + if e.URL == "" { + return nil, errors.New(ErrBadURL) + } + + return url.Parse(e.URL) +} + +// IsMention returns true if the type of the message entity is "mention" (@username). +func (e MessageEntity) IsMention() bool { + return e.Type == "mention" +} + +// IsTextMention returns true if the type of the message entity is "text_mention" +// (At this time, the user field exists, and occurs when tagging a member without a username) +func (e MessageEntity) IsTextMention() bool { + return e.Type == "text_mention" +} + +// IsHashtag returns true if the type of the message entity is "hashtag". +func (e MessageEntity) IsHashtag() bool { + return e.Type == "hashtag" +} + +// IsCommand returns true if the type of the message entity is "bot_command". +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 { + return e.Type == "url" +} + +// IsEmail returns true if the type of the message entity is "email". +func (e MessageEntity) IsEmail() bool { + return e.Type == "email" +} + +// IsBold returns true if the type of the message entity is "bold" (bold text). +func (e MessageEntity) IsBold() bool { + return e.Type == "bold" +} + +// IsItalic returns true if the type of the message entity is "italic" (italic text). +func (e MessageEntity) IsItalic() bool { + return e.Type == "italic" +} + +// IsCode returns true if the type of the message entity is "code" (monowidth string). +func (e MessageEntity) IsCode() bool { + return e.Type == "code" +} + +// IsPre returns true if the type of the message entity is "pre" (monowidth block). +func (e MessageEntity) IsPre() bool { + return e.Type == "pre" +} + +// IsTextLink returns true if the type of the message entity is "text_link" (clickable text URL). +func (e MessageEntity) IsTextLink() bool { + return e.Type == "text_link" +} + +// 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 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 + Height int `json:"height"` + // FileSize file size + // + // optional + FileSize int `json:"file_size,omitempty"` +} + +// Animation represents an animation file. +type Animation struct { + // FileID is the 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 + 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 + // + // optional + Thumbnail *PhotoSize `json:"thumb,omitempty"` + // FileName original animation filename as defined by sender + // + // optional + FileName string `json:"file_name,omitempty"` + // MimeType of the file as defined by sender + // + // optional + MimeType string `json:"mime_type,omitempty"` + // FileSize file size + // + // optional + FileSize int64 `json:"file_size,omitempty"` +} + +// 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"` + // 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 + Performer string `json:"performer,omitempty"` + // Title of the audio as defined by sender or by audio tags + // + // optional + Title string `json:"title,omitempty"` + // FileName is the original filename as defined by sender + // + // optional + FileName string `json:"file_name,omitempty"` + // MimeType of the file as defined by sender + // + // optional + MimeType string `json:"mime_type,omitempty"` + // FileSize file size + // + // optional + FileSize int64 `json:"file_size,omitempty"` + // Thumbnail is the album cover to which the music file belongs + // + // optional + Thumbnail *PhotoSize `json:"thumb,omitempty"` +} + +// Document represents a general file. +type Document 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 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,omitempty"` + // FileName original filename as defined by sender + // + // optional + FileName string `json:"file_name,omitempty"` + // MimeType of the file as defined by sender + // + // optional + MimeType string `json:"mime_type,omitempty"` + // FileSize file size + // + // optional + FileSize int64 `json:"file_size,omitempty"` +} + +// Video represents a video file. +type Video 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"` + // 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 video thumbnail + // + // optional + 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,omitempty"` + // FileSize file size + // + // optional + FileSize int64 `json:"file_size,omitempty"` +} + +// 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 + Duration int `json:"duration"` + // Thumbnail video thumbnail + // + // optional + Thumbnail *PhotoSize `json:"thumb,omitempty"` + // FileSize file size + // + // optional + FileSize int `json:"file_size,omitempty"` +} + +// 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,omitempty"` + // FileSize file size + // + // optional + FileSize int64 `json:"file_size,omitempty"` +} + +// Contact represents a phone contact. +// +// Note that LastName and UserID may be empty. +type Contact struct { + // PhoneNumber contact's phone number + PhoneNumber string `json:"phone_number"` + // FirstName contact's first name + FirstName string `json:"first_name"` + // LastName contact's last name + // + // optional + LastName string `json:"last_name,omitempty"` + // UserID contact's user identifier in Telegram + // + // optional + 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"` + // ExplanationEntities 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 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 represents a venue. +type Venue struct { + // Location is the venue location + Location Location `json:"location"` + // Title is the name of the venue + Title string `json:"title"` + // Address of the venue + Address string `json:"address"` + // FoursquareID is the foursquare identifier of the venue + // + // optional + 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"` +} + +// WebAppData Contains data sent from a Web App to the bot. +type WebAppData struct { + // Data is the data. Be aware that a bad client can send arbitrary data in this field. + Data string `json:"data"` + // ButtonText is the text of the web_app keyboard button, from which the Web App + // was opened. Be aware that a bad client can send arbitrary data in this field. + ButtonText string `json:"button_text"` +} + +// 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"` +} + +// ForumTopicCreated represents a service message about a new forum topic +// created in the chat. +type ForumTopicCreated struct { + // Name is the name of topic + Name string `json:"name"` + // IconColor is the color of the topic icon in RGB format + IconColor int `json:"icon_color"` + // IconCustomEmojiID is the unique identifier of the custom emoji + // shown as the topic icon + // + // optional + IconCustomEmojiID string `json:"icon_custom_emoji_id,omitempty"` +} + +// ForumTopicClosed represents a service message about a forum topic +// closed in the chat. Currently holds no information. +type ForumTopicClosed struct { +} + +// ForumTopicEdited object represents a service message about an edited forum topic. +type ForumTopicEdited struct { + // Name is the new name of the topic, if it was edited + // + // optional + Name string `json:"name,omitempty"` + // IconCustomEmojiID is the new identifier of the custom emoji + // shown as the topic icon, if it was edited; + // an empty string if the icon was removed + // + // optional + IconCustomEmojiID *string `json:"icon_custom_emoji_id,omitempty"` +} + +// ForumTopicReopened represents a service message about a forum topic +// reopened in the chat. Currently holds no information. +type ForumTopicReopened struct { +} + +// GeneralForumTopicHidden represents a service message about General forum topic +// hidden in the chat. Currently holds no information. +type GeneralForumTopicHidden struct { +} + +// GeneralForumTopicUnhidden represents a service message about General forum topic +// unhidden in the chat. Currently holds no information. +type GeneralForumTopicUnhidden struct { +} + +// UserShared object contains information about the user whose identifier +// was shared with the bot using a KeyboardButtonRequestUser button. +type UserShared struct { + // RequestID is an indentifier of the request. + RequestID int `json:"request_id"` + // UserID in an identifier of the shared user. + UserID int64 `json:"user_id"` +} + +// ChatShared contains information about the chat whose identifier +// was shared with the bot using a KeyboardButtonRequestChat button. +type ChatShared struct { + // RequestID is an indentifier of the request. + RequestID int `json:"request_id"` + // ChatID is an identifier of the shared chat. + ChatID int64 `json:"chat_id"` +} + +// WriteAccessAllowed represents a service message about a user +// allowing a bot added to the attachment menu to write messages. +// Currently holds no information. +type WriteAccessAllowed struct { +} + +// VideoChatScheduled represents a service message about a voice chat scheduled +// in the chat. +type VideoChatScheduled 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 *VideoChatScheduled) Time() time.Time { + return time.Unix(int64(m.StartDate), 0) +} + +// VideoChatStarted represents a service message about a voice chat started in +// the chat. +type VideoChatStarted struct{} + +// VideoChatEnded represents a service message about a voice chat ended in the +// chat. +type VideoChatEnded struct { + // Voice chat duration; in seconds. + Duration int `json:"duration"` +} + +// VideoChatParticipantsInvited represents a service message about new members +// invited to a voice chat. +type VideoChatParticipantsInvited struct { + // New members that were invited to the voice chat. + // + // optional + Users []User `json:"users,omitempty"` +} + +// 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) + Photos [][]PhotoSize `json:"photos"` +} + +// 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 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 int64 `json:"file_size,omitempty"` + // FilePath file path + // + // optional + 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. +func (f *File) Link(token string) string { + return fmt.Sprintf(FileEndpoint, token, f.FilePath) +} + +// WebAppInfo contains information about a Web App. +type WebAppInfo struct { + // URL is the HTTPS URL of a Web App to be opened with additional data as + // specified in Initializing Web Apps. + URL string `json:"url"` +} + +// 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"` + // IsPersistent requests clients to always show the keyboard + // when the regular keyboard is hidden. + // Defaults to false, in which case the custom keyboard can be hidden + // and opened with a keyboard icon. + // + // optional + IsPersistent bool `json:"is_persistent"` + // ResizeKeyboard requests clients to resize the keyboard vertically for optimal fit + // (e.g., make the keyboard smaller if there are just two rows of buttons). + // Defaults to false, in which case the custom keyboard + // is always of the same height as the app's standard keyboard. + // + // optional + 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 + // in the input field to see the custom keyboard again. + // Defaults to false. + // + // optional + 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; + // 2) if the bot's message is a reply (has Message.ReplyToMessage not nil), sender of the original message. + // + // Example: A user requests to change the bot's language, + // bot replies to the request with a keyboard to select the new language. + // Other users in the group don't see the keyboard. + // + // optional + Selective bool `json:"selective,omitempty"` +} + +// 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. + Text string `json:"text"` + // RequestUser if specified, pressing the button will open + // a list of suitable users. Tapping on any user will send + // their identifier to the bot in a "user_shared" service message. + // Available in private chats only. + // + // optional + RequestUser *KeyboardButtonRequestUser `json:"request_user,omitempty"` + // RequestChat if specified, pressing the button will open + // a list of suitable chats. Tapping on a chat will send + // its identifier to the bot in a "chat_shared" service message. + // Available in private chats only. + // + // optional + RequestChat *KeyboardButtonRequestChat `json:"request_chat,omitempty"` + // RequestContact if True, the user's phone number will be sent + // as a contact when the button is pressed. + // Available in private chats only. + // + // optional + 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,omitempty"` + // RequestPoll if specified, 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"` + // WebApp if specified, the described Web App will be launched when the button + // is pressed. The Web App will be able to send a “web_app_data” service + // message. Available in private chats only. + // + // optional + WebApp *WebAppInfo `json:"web_app,omitempty"` +} + +// KeyboardButtonRequestUser defines the criteria used to request +// a suitable user. The identifier of the selected user will be shared +// with the bot when the corresponding button is pressed. +type KeyboardButtonRequestUser struct { + // RequestID is a signed 32-bit identifier of the request. + RequestID int `json:"request_id"` + // UserIsBot pass True to request a bot, + // pass False to request a regular user. + // If not specified, no additional restrictions are applied. + // + // optional + UserIsBot bool `json:"user_is_bot,omitempty"` + // UserIsPremium pass True to request a premium user, + // pass False to request a non-premium user. + // If not specified, no additional restrictions are applied. + // + // optional + UserIsPremium bool `json:"user_is_premium,omitempty"` +} + +// KeyboardButtonRequestChat defines the criteria used to request +// a suitable chat. The identifier of the selected chat will be shared +// with the bot when the corresponding button is pressed. +type KeyboardButtonRequestChat struct { + // RequestID is a signed 32-bit identifier of the request. + RequestID int `json:"request_id"` + // ChatIsChannel pass True to request a channel chat, + // pass False to request a group or a supergroup chat. + ChatIsChannel bool `json:"chat_is_channel"` + // ChatIsForum pass True to request a forum supergroup, + // pass False to request a non-forum chat. + // If not specified, no additional restrictions are applied. + // + // optional + ChatIsForum bool `json:"chat_is_forum,omitempty"` + // ChatHasUsername pass True to request a supergroup or a channel with a username, + // pass False to request a chat without a username. + // If not specified, no additional restrictions are applied. + // + // optional + ChatHasUsername bool `json:"chat_has_username,omitempty"` + // ChatIsCreated pass True to request a chat owned by the user. + // Otherwise, no additional restrictions are applied. + // + // optional + ChatIsCreated bool `json:"chat_is_created,omitempty"` + // UserAdministratorRights is a JSON-serialized object listing + // the required administrator rights of the user in the chat. + // If not specified, no additional restrictions are applied. + // + // optional + UserAdministratorRights *ChatAdministratorRights `json:"user_administrator_rights,omitempty"` + // BotAdministratorRights is a JSON-serialized object listing + // the required administrator rights of the bot in the chat. + // The rights must be a subset of user_administrator_rights. + // If not specified, no additional restrictions are applied. + // + // optional + BotAdministratorRights *ChatAdministratorRights `json:"bot_administrator_rights,omitempty"` + // BotIsMember pass True to request a chat with the bot as a member. + // Otherwise, no additional restrictions are applied. + // + // optional + BotIsMember bool `json:"bot_is_member,omitempty"` +} + +// KeyboardButtonPollType represents type of 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 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; + // if you want to hide the keyboard from sight but keep it accessible, + // use one_time_keyboard in ReplyKeyboardMarkup). + RemoveKeyboard bool `json:"remove_keyboard"` + // Selective use this parameter if you want to remove the keyboard for 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. + // + // Example: A user votes in a poll, bot returns confirmation message + // in reply to the vote and removes the keyboard for that user, + // while still showing the keyboard with poll options to users who haven't voted yet. + // + // optional + Selective bool `json:"selective,omitempty"` +} + +// 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 [][]InlineKeyboardButton `json:"inline_keyboard"` +} + +// 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. +// +// CallbackGame, if set, MUST be first button in first row. +type InlineKeyboardButton struct { + // Text label text on the button + Text string `json:"text"` + // URL HTTP or tg:// url to be opened when button is pressed. + // + // 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 + CallbackData *string `json:"callback_data,omitempty"` + // WebApp is the Description of the Web App that will be launched when the user presses the button. + // The Web App will be able to send an arbitrary message on behalf of the user using the method + // answerWebAppQuery. Available only in private chats between a user and the bot. + // + // optional + WebApp *WebAppInfo `json:"web_app,omitempty"` + // SwitchInlineQuery if set, pressing the button will prompt the user to select one of their chats, + // open that chat and insert the bot's username and the specified inline query in the input field. + // Can be empty, in which case just the bot's username will be inserted. + // + // This offers an easy way for users to start using your bot + // in inline mode when they are currently in a private chat with it. + // Especially useful when combined with switch_pm… actions – in this case + // the user will be automatically returned to the chat they switched from, + // skipping the chat selection screen. + // + // optional + SwitchInlineQuery *string `json:"switch_inline_query,omitempty"` + // SwitchInlineQueryCurrentChat if set, pressing the button will insert the bot's username + // and the specified inline query in the current chat's input field. + // Can be empty, in which case only the bot's username will be inserted. + // + // This offers a quick way for the user to open your bot in inline mode + // in the same chat – good for selecting something from multiple options. + // + // optional + SwitchInlineQueryCurrentChat *string `json:"switch_inline_query_current_chat,omitempty"` + // CallbackGame description of the game that will be launched when the user presses the button. + // + // optional + CallbackGame *CallbackGame `json:"callback_game,omitempty"` + // Pay specify True, to send a Pay button. + // + // NOTE: This type of button must always be the first button in the first row. + // + // optional + Pay bool `json:"pay,omitempty"` +} + +// 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. + // + // optional + 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,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,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,omitempty"` +} + +// 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,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,omitempty"` + // 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,omitempty"` + // ExpireDate is the point in time (Unix timestamp) when the link will + // expire or has been expired. + // + // optional + ExpireDate int `json:"expire_date,omitempty"` + // 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,omitempty"` + // PendingJoinRequestCount is the number of pending join requests created + // using this link. + // + // optional + PendingJoinRequestCount int `json:"pending_join_request_count,omitempty"` +} + +type ChatAdministratorRights struct { + IsAnonymous bool `json:"is_anonymous"` + CanManageChat bool `json:"can_manage_chat"` + CanDeleteMessages bool `json:"can_delete_messages"` + CanManageVideoChats bool `json:"can_manage_video_chats"` + CanRestrictMembers bool `json:"can_restrict_members"` + CanPromoteMembers bool `json:"can_promote_members"` + CanChangeInfo bool `json:"can_change_info"` + CanInviteUsers bool `json:"can_invite_users"` + CanPostMessages bool `json:"can_post_messages"` + CanEditMessages bool `json:"can_edit_messages"` + CanPinMessages bool `json:"can_pin_messages"` + CanManageTopics bool `json:"can_manage_topics"` +} + +// ChatMember contains information about one member of a chat. +type ChatMember struct { + // User information about the user + User *User `json:"user"` + // Status the member's status in the chat. + // Can be + // “creator”, + // “administrator”, + // “member”, + // “restricted”, + // “left” or + // “kicked” + Status string `json:"status"` + // CustomTitle owner and administrators only. Custom title for this user + // + // 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,omitempty"` + // UntilDate restricted and kicked only. + // Date when restrictions will be lifted for this user; + // unix time. + // + // optional + UntilDate int64 `json:"until_date,omitempty"` + // CanBeEdited administrators only. + // True, if the bot is allowed to edit administrator privileges of that user. + // + // optional + CanBeEdited bool `json:"can_be_edited,omitempty"` + // 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 supergroups and ignore slow mode. Implied by + // any other administrator privilege. + // + // optional + CanManageChat bool `json:"can_manage_chat,omitempty"` + // CanPostMessages administrators only. + // True, if the administrator can post in the channel; + // channels only. + // + // optional + CanPostMessages bool `json:"can_post_messages,omitempty"` + // CanEditMessages administrators only. + // True, if the administrator can edit messages of other users and can pin messages; + // channels only. + // + // optional + CanEditMessages bool `json:"can_edit_messages,omitempty"` + // CanDeleteMessages administrators only. + // True, if the administrator can delete messages of other users. + // + // optional + CanDeleteMessages bool `json:"can_delete_messages,omitempty"` + // CanManageVideoChats administrators only. + // True, if the administrator can manage video chats. + // + // optional + CanManageVideoChats bool `json:"can_manage_video_chats,omitempty"` + // CanRestrictMembers administrators only. + // True, if the administrator can restrict, ban or unban chat members. + // + // optional + CanRestrictMembers bool `json:"can_restrict_members,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, + // directly or indirectly (promoted by administrators that were appointed by the user). + // + // 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"` + // CanManageTopics administrators and restricted only. + // True, if the user is allowed to create, rename, + // close, and reopen forum topics; supergroups only + // + // optional + CanManageTopics bool `json:"can_manage_topics,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 + CanSendMessages bool `json:"can_send_messages,omitempty"` + // CanSendAudios restricted only. + // True, if the user is allowed to send audios + // + // optional + CanSendAudios bool + // CanSendDocuments restricted only. + // True, if the user is allowed to send documents + // + // optional + CanSendDocuments bool + // CanSendPhotos is restricted only. + // True, if the user is allowed to send photos + // + // optional + CanSendPhotos bool + // CanSendVideos restricted only. + // True, if the user is allowed to send videos + // + // optional + CanSendVideos bool + // CanSendVideoNotes restricted only. + // True, if the user is allowed to send video notes + // + // optional + CanSendVideoNotes bool + // CanSendVoiceNotes restricted only. + // True, if the user is allowed to send voice notes + // + // optional + CanSendVoiceNotes bool + // 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. + // + // optional + CanSendOtherMessages bool `json:"can_send_other_messages,omitempty"` + // CanAddWebPagePreviews restricted only. + // True, if the user is allowed to add web page previews to their messages. + // + // optional + CanAddWebPagePreviews bool `json:"can_add_web_page_previews,omitempty"` +} + +// IsCreator returns if the ChatMember was the creator of the chat. +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" } + +// 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" } + +// SetCanSendMediaMessages is a method to replace field "can_send_media_messages". +// It sets CanSendAudio, CanSendDocuments, CanSendPhotos, CanSendVideos, +// CanSendVideoNotes, CanSendVoiceNotes to passed value. +func (chat *ChatMember) SetCanSendMediaMessages(b bool) { + chat.CanSendAudios = b + chat.CanSendDocuments = b + chat.CanSendPhotos = b + chat.CanSendVideos = b + chat.CanSendVideoNotes = b + chat.CanSendVoiceNotes = b +} + +// CanSendMediaMessages method to replace field "can_send_media_messages". +// It returns true if CanSendAudio and CanSendDocuments and CanSendPhotos and CanSendVideos and +// CanSendVideoNotes and CanSendVoiceNotes are true. +func (chat *ChatMember) CanSendMediaMessages() bool { + return chat.CanSendAudios && chat.CanSendDocuments && + chat.CanSendPhotos && chat.CanSendVideos && + chat.CanSendVideoNotes && chat.CanSendVoiceNotes +} + +// 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 + InviteLink *ChatInviteLink `json:"invite_link,omitempty"` +} + +// 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:"from"` + // UserChatID identifier of a private chat with the user who sent the join request. + UserChatID int64 `json:"user_chat_id"` + // Date the request was sent in Unix time. + Date int `json:"date"` + // Bio of the user. + // + // optional + Bio string `json:"bio,omitempty"` + // InviteLink is the link that was used by the user to send the join request. + // + // optional + InviteLink *ChatInviteLink `json:"invite_link,omitempty"` +} + +// 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 + CanSendMessages bool `json:"can_send_messages,omitempty"` + // CanSendAudios is true, if the user is allowed to send audios + // + // optional + CanSendAudios bool + // CanSendDocuments is true, if the user is allowed to send documents + // + // optional + CanSendDocuments bool + // CanSendPhotos is true, if the user is allowed to send photos + // + // optional + CanSendPhotos bool + // CanSendVideos is true, if the user is allowed to send videos + // + // optional + CanSendVideos bool + // CanSendVideoNotes is true, if the user is allowed to send video notes + // + // optional + CanSendVideoNotes bool + // CanSendVoiceNotes is true, if the user is allowed to send voice notes + // + // optional + CanSendVoiceNotes bool + // CanSendPolls is true, if the user is allowed to send polls, implies + // can_send_messages + // + // optional + 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 + 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 + 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 + 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 + CanPinMessages bool `json:"can_pin_messages,omitempty"` + // CanManageTopics is true, if the user is allowed to create forum topics. + // If omitted defaults to the value of can_pin_messages + // + // optional + CanManageTopics bool `json:"can_manage_topics,omitempty"` +} + +// SetCanSendMediaMessages is a method to replace field "can_send_media_messages". +// It sets CanSendAudio, CanSendDocuments, CanSendPhotos, CanSendVideos, +// CanSendVideoNotes, CanSendVoiceNotes to passed value. +func (c *ChatPermissions) SetCanSendMediaMessages(b bool) { + c.CanSendAudios = b + c.CanSendDocuments = b + c.CanSendPhotos = b + c.CanSendVideos = b + c.CanSendVideoNotes = b + c.CanSendVoiceNotes = b +} + +// CanSendMediaMessages method to replace field "can_send_media_messages". +// It returns true if CanSendAudio and CanSendDocuments and CanSendPhotos and CanSendVideos and +// CanSendVideoNotes and CanSendVoiceNotes are true. +func (c *ChatPermissions) CanSendMediaMessages() bool { + return c.CanSendAudios && c.CanSendDocuments && + c.CanSendPhotos && c.CanSendVideos && + c.CanSendVideoNotes && c.CanSendVoiceNotes +} + +// 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"` +} + +// ForumTopic represents a forum topic. +type ForumTopic struct { + // MessageThreadID is the unique identifier of the forum topic + MessageThreadID int `json:"message_thread_id"` + // Name is the name of the topic + Name string `json:"name"` + // IconColor is the color of the topic icon in RGB format + IconColor int `json:"icon_color"` + // IconCustomEmojiID is the unique identifier of the custom emoji + // shown as the topic icon + // + // optional + IconCustomEmojiID string `json:"icon_custom_emoji_id,omitempty"` +} + +// 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"` +} + +// MenuButton describes the bot's menu button in a private chat. +type MenuButton struct { + // Type is the type of menu button, must be one of: + // - `commands` + // - `web_app` + // - `default` + Type string `json:"type"` + // Text is the text on the button, for `web_app` type. + Text string `json:"text,omitempty"` + // WebApp is the description of the Web App that will be launched when the + // user presses the button for the `web_app` type. + WebApp *WebAppInfo `json:"web_app,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 + 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 + RetryAfter int `json:"retry_after,omitempty"` +} + +// 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://<file_attach_name>” to upload a new one + // using multipart/form-data under <file_attach_name> name. + 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,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"` + // HasSpoiler pass True, if the photo needs to be covered with a spoiler animation + // + // optional + HasSpoiler bool `json:"has_spoiler,omitempty"` +} + +// 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,omitempty"` + // Height video height + // + // optional + Height int `json:"height,omitempty"` + // Duration video duration + // + // optional + Duration int `json:"duration,omitempty"` + // SupportsStreaming pass True, if the uploaded video is suitable for streaming. + // + // optional + SupportsStreaming bool `json:"supports_streaming,omitempty"` + // HasSpoiler pass True, if the video needs to be covered with a spoiler animation + // + // optional + HasSpoiler bool `json:"has_spoiler,omitempty"` +} + +// 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 + Thumb RequestFileData `json:"thumb,omitempty"` + // Width video width + // + // optional + Width int `json:"width,omitempty"` + // Height video height + // + // optional + Height int `json:"height,omitempty"` + // Duration video duration + // + // optional + Duration int `json:"duration,omitempty"` + // HasSpoiler pass True, if the photo needs to be covered with a spoiler animation + // + // optional + HasSpoiler bool `json:"has_spoiler,omitempty"` +} + +// InputMediaAudio is an 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 + Thumb RequestFileData `json:"thumb,omitempty"` + // Duration of the audio in seconds + // + // optional + Duration int `json:"duration,omitempty"` + // Performer of the audio + // + // optional + Performer string `json:"performer,omitempty"` + // Title of the audio + // + // optional + Title string `json:"title,omitempty"` +} + +// 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. + // + // 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 + // + // optional + DisableContentTypeDetection bool `json:"disable_content_type_detection,omitempty"` +} + +// Constant values for sticker types +const ( + StickerTypeRegular = "regular" + StickerTypeMask = "mask" + StickerTypeCustomEmoji = "custom_emoji" +) + +// 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 a 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"` + // Type is a type of the sticker, currently one of “regular”, + // “mask”, “custom_emoji”. The type of the sticker is independent + // from its format, which is determined by the fields is_animated and is_video. + Type string `json:"type"` + // Width sticker width + Width int `json:"width"` + // Height sticker height + Height int `json:"height"` + // IsAnimated true, if the sticker is animated + // + // optional + IsAnimated bool `json:"is_animated,omitempty"` + // IsVideo true, if the sticker is a video sticker + // + // optional + IsVideo bool `json:"is_video,omitempty"` + // Thumbnail sticker thumbnail in the .WEBP or .JPG format + // + // optional + Thumbnail *PhotoSize `json:"thumb,omitempty"` + // Emoji associated with the sticker + // + // optional + Emoji string `json:"emoji,omitempty"` + // SetName of the sticker set to which the sticker belongs + // + // optional + SetName string `json:"set_name,omitempty"` + // PremiumAnimation for premium regular stickers, premium animation for the sticker + // + // optional + PremiumAnimation *File `json:"premium_animation,omitempty"` + // MaskPosition is for mask stickers, the position where the mask should be + // placed + // + // optional + MaskPosition *MaskPosition `json:"mask_position,omitempty"` + // CustomEmojiID for custom emoji stickers, unique identifier of the custom emoji + // + // optional + CustomEmojiID string `json:"custom_emoji_id,omitempty"` + // FileSize + // + // optional + FileSize int `json:"file_size,omitempty"` +} + +// IsRegular returns if the Sticker is regular +func (s Sticker) IsRegular() bool { + return s.Type == StickerTypeRegular +} + +// IsMask returns if the Sticker is mask +func (s Sticker) IsMask() bool { + return s.Type == StickerTypeMask +} + +// IsCustomEmoji returns if the Sticker is custom emoji +func (s Sticker) IsCustomEmoji() bool { + return s.Type == StickerTypeCustomEmoji +} + +// StickerSet represents a sticker set. +type StickerSet struct { + // Name sticker set name + Name string `json:"name"` + // Title sticker set title + Title string `json:"title"` + // StickerType of stickers in the set, currently one of “regular”, “mask”, “custom_emoji” + StickerType string `json:"sticker_type"` + // IsAnimated true, if the sticker set contains animated stickers + IsAnimated bool `json:"is_animated"` + // IsVideo true, if the sticker set contains video stickers + IsVideo bool `json:"is_video"` + // ContainsMasks true, if the sticker set contains masks + // + // deprecated. Use sticker_type instead + 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"` +} + +// IsRegular returns if the StickerSet is regular +func (s StickerSet) IsRegular() bool { + return s.StickerType == StickerTypeRegular +} + +// IsMask returns if the StickerSet is mask +func (s StickerSet) IsMask() bool { + return s.StickerType == StickerTypeMask +} + +// IsCustomEmoji returns if the StickerSet is custom emoji +func (s StickerSet) IsCustomEmoji() bool { + return s.StickerType == StickerTypeCustomEmoji +} + +// 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 + Text string `json:"text,omitempty"` + // TextEntities special entities that appear in text, such as usernames, URLs, bot commands, etc. + // + // optional + TextEntities []MessageEntity `json:"text_entities,omitempty"` + // Animation is an animation that will be displayed in the game message in chats. + // Upload via BotFather (https://t.me/botfather). + // + // optional + Animation Animation `json:"animation,omitempty"` +} + +// 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 + // + // 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. + // + // 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. + // + // optional + LastErrorMessage string `json:"last_error_message,omitempty"` + // LastSynchronizationErrorDate is the unix time of the most recent error that + // happened when trying to synchronize available updates with Telegram datacenters. + LastSynchronizationErrorDate int `json:"last_synchronization_error_date,omitempty"` + // MaxConnections maximum allowed number of simultaneous + // HTTPS connections to the webhook for update delivery. + // + // optional + MaxConnections int `json:"max_connections,omitempty"` + // AllowedUpdates is a list of update types the bot is subscribed to. + // Defaults to all update types + // + // optional + 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 + ChatType string `json:"chat_type,omitempty"` + // 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,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 audio + // + // optional + InputMessageContent interface{} `json:"input_message_content,omitempty"` +} + +// InlineQueryResultCachedDocument is an inline query response with cached document. +type InlineQueryResultCachedDocument struct { + // Type of the result, must be a document + Type string `json:"type"` + // ID unique identifier for this result, 1-64 bytes + ID string `json:"id"` + // DocumentID a valid file identifier for the file + DocumentID string `json:"document_file_id"` + // Title for the result + // + // optional + Title string `json:"title,omitempty"` + // Caption of the document to be sent, 0-1024 characters after entities parsing + // + // optional + Caption string `json:"caption,omitempty"` + // Description short description of the result + // + // optional + 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 + 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 file + // + // optional + InputMessageContent interface{} `json:"input_message_content,omitempty"` +} + +// InlineQueryResultCachedGIF is an inline query response with cached gif. +type InlineQueryResultCachedGIF struct { + // Type of the result, must be gif. + Type string `json:"type"` + // ID unique identifier for this result, 1-64 bytes. + ID string `json:"id"` + // GifID a valid file identifier for the GIF file. + GIFID string `json:"gif_file_id"` + // 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,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,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 GIF animation. + // + // optional + InputMessageContent interface{} `json:"input_message_content,omitempty"` +} + +// InlineQueryResultCachedMPEG4GIF is an inline query response with cached +// H.264/MPEG-4 AVC video without sound gif. +type InlineQueryResultCachedMPEG4GIF struct { + // Type of the result, must be mpeg4_gif + Type string `json:"type"` + // ID unique identifier for this result, 1-64 bytes + ID string `json:"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,omitempty"` + // Caption of the MPEG-4 file to be sent, 0-1024 characters after entities parsing. + // + // optional + 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,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 + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` + // InputMessageContent content of the message to be sent instead of the video animation. + // + // optional + InputMessageContent interface{} `json:"input_message_content,omitempty"` +} + +// InlineQueryResultCachedPhoto is an inline query response with cached photo. +type InlineQueryResultCachedPhoto struct { + // Type of the result, must be a photo. + Type string `json:"type"` + // ID unique identifier for this result, 1-64 bytes. + ID string `json:"id"` + // PhotoID a valid file identifier of the photo. + PhotoID string `json:"photo_file_id"` + // Title for the result. + // + // optional + Title string `json:"title,omitempty"` + // Description short description of the result. + // + // optional + Description string `json:"description,omitempty"` + // Caption of the photo to be sent, 0-1024 characters after entities parsing. + // + // optional + 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 + 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. + // + // optional + 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 a 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 sticker + // + // optional + InputMessageContent interface{} `json:"input_message_content,omitempty"` +} + +// InlineQueryResultCachedVideo is an inline query response with cached video. +type InlineQueryResultCachedVideo 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"` + // VideoID a valid file identifier for the video file + VideoID string `json:"video_file_id"` + // Title for the result + Title string `json:"title"` + // Description short description of the result + // + // optional + Description string `json:"description,omitempty"` + // Caption of the video to be sent, 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,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 video + // + // optional + InputMessageContent interface{} `json:"input_message_content,omitempty"` +} + +// 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 + ID string `json:"id"` + // 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,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 + // + // optional + InputMessageContent interface{} `json:"input_message_content,omitempty"` +} + +// 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. + ID string `json:"id"` + // 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. + // + // optional + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` + // URL of the result. + // + // optional + URL string `json:"url,omitempty"` + // HideURL pass True, if you don't want the URL to be shown in the message. + // + // optional + HideURL bool `json:"hide_url,omitempty"` + // Description short description of the result. + // + // optional + Description string `json:"description,omitempty"` + // ThumbURL url of the thumbnail for the result + // + // 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"` +} + +// InlineQueryResultAudio is an inline query response audio. +type InlineQueryResultAudio 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"` + // 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,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"` + // 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 + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` + // InputMessageContent content of the message to be sent instead of the audio + // + // optional + InputMessageContent interface{} `json:"input_message_content,omitempty"` +} + +// 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 + ID string `json:"id"` + // GameShortName short name of the game + GameShortName string `json:"game_short_name"` + // ReplyMarkup inline keyboard attached to the message + // + // optional + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` +} + +// InlineQueryResultDocument is an inline query response document. +type InlineQueryResultDocument struct { + // Type of the result, must be a 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 of the document to be sent, 0-1024 characters after entities parsing + // + // optional + 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 + 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 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"` +} + +// 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. + ID string `json:"id"` + // 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 + // + // optional + Width int `json:"gif_width,omitempty"` + // Height of the GIF + // + // 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,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"` + // 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. + // + // 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 + Type string `json:"type"` + // ID unique identifier for this result, 1-64 Bytes + ID string `json:"id"` + // 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"` + // HorizontalAccuracy is the radius of uncertainty for the location, + // measured in meters; 0-1500 + // + // optional + HorizontalAccuracy float64 `json:"horizontal_accuracy,omitempty"` + // 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,omitempty"` + // 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,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 location + // + // optional + InputMessageContent interface{} `json:"input_message_content,omitempty"` + // ThumbURL url of the thumbnail for the result + // + // 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"` +} + +// 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 + ID string `json:"id"` + // 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,omitempty"` + // Height vVideo height + // + // optional + Height int `json:"mpeg4_height,omitempty"` + // Duration video duration + // + // optional + Duration int `json:"mpeg4_duration,omitempty"` + // 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,omitempty"` + // Caption of the MPEG-4 file to be sent, 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,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 video animation + // + // optional + InputMessageContent interface{} `json:"input_message_content,omitempty"` +} + +// 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. + 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 + Width int `json:"photo_width,omitempty"` + // Height of the photo + // + // optional + Height int `json:"photo_height,omitempty"` + // ThumbURL url of the thumbnail for the photo. + // + // optional + ThumbURL string `json:"thumb_url,omitempty"` + // Title for the result + // + // optional + Title string `json:"title,omitempty"` + // Description short description of the result + // + // optional + Description string `json:"description,omitempty"` + // Caption of the photo to be sent, 0-1024 characters after entities parsing. + // + // optional + 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 + 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 + InputMessageContent interface{} `json:"input_message_content,omitempty"` +} + +// InlineQueryResultVenue is an inline query response venue. +type InlineQueryResultVenue struct { + // Type of the result, must be venue + Type string `json:"type"` + // ID unique identifier for this result, 1-64 Bytes + ID string `json:"id"` + // Latitude of the venue location in degrees + Latitude float64 `json:"latitude"` + // Longitude of the venue location in degrees + Longitude float64 `json:"longitude"` + // Title of the venue + Title string `json:"title"` + // Address of the venue + Address string `json:"address"` + // FoursquareID foursquare identifier of the venue if known + // + // optional + 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,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 + ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` + // InputMessageContent content of the message to be sent instead of the venue + // + // optional + InputMessageContent interface{} `json:"input_message_content,omitempty"` + // ThumbURL url of the thumbnail for the result + // + // 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"` +} + +// 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). + // + // 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"` + // 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 + // + // 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 +type ChosenInlineResult struct { + // ResultID the unique identifier for the result that was chosen + ResultID string `json:"result_id"` + // From the user that chose the result + From *User `json:"from"` + // Location sender location, only for bots that require user location + // + // optional + 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,omitempty"` + // Query the query that was used to obtain the result + Query string `json:"query"` +} + +// SentWebAppMessage contains information about an inline message sent by a Web App +// on behalf of a user. +type SentWebAppMessage struct { + // Identifier of the sent inline message. Available only if there is an inline + // keyboard attached to the message. + // + // optional + InlineMessageID string `json:"inline_message_id,omitempty"` +} + +// InputTextMessageContent contains text for displaying +// as an inline query result. +type InputTextMessageContent struct { + // Text of the message to be sent, 1-4096 characters + Text string `json:"message_text"` + // ParseMode mode for parsing entities in the message text. + // See formatting options for more details + // (https://core.telegram.org/bots/api#formatting-options). + // + // optional + 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,omitempty"` +} + +// InputLocationMessageContent contains a location for displaying +// as an inline query result. +type InputLocationMessageContent struct { + // Latitude of the location in degrees + 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,omitempty"` + // 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,omitempty"` + // 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,omitempty"` +} + +// InputVenueMessageContent contains a venue for displaying +// as an inline query result. +type InputVenueMessageContent struct { + // Latitude of the venue in degrees + Latitude float64 `json:"latitude"` + // Longitude of the venue in degrees + Longitude float64 `json:"longitude"` + // Title name of the venue + Title string `json:"title"` + // Address of the venue + Address string `json:"address"` + // FoursquareID foursquare identifier of the venue, if known + // + // optional + 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,omitempty"` + // GooglePlaceType is the Google Places type of the venue + // + // optional + GooglePlaceType string `json:"google_place_type,omitempty"` +} + +// InputContactMessageContent contains a contact for displaying +// as an inline query result. +type InputContactMessageContent struct { + // PhoneNumber contact's phone number + PhoneNumber string `json:"phone_number"` + // FirstName contact's first name + FirstName string `json:"first_name"` + // LastName contact's last name + // + // optional + 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. +type Invoice struct { + // Title product name + Title string `json:"title"` + // Description product description + Description string `json:"description"` + // StartParameter unique bot deep-linking parameter that can be used to generate this invoice + StartParameter string `json:"start_parameter"` + // Currency three-letter ISO 4217 currency code + // (see https://core.telegram.org/bots/payments#supported-currencies) + Currency string `json:"currency"` + // TotalAmount total price 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). + TotalAmount int `json:"total_amount"` +} + +// ShippingAddress represents a shipping address. +type ShippingAddress struct { + // CountryCode ISO 3166-1 alpha-2 country code + CountryCode string `json:"country_code"` + // State if applicable + State string `json:"state"` + // City city + City string `json:"city"` + // StreetLine1 first line for the address + StreetLine1 string `json:"street_line1"` + // StreetLine2 second line for the address + StreetLine2 string `json:"street_line2"` + // PostCode address post code + PostCode string `json:"post_code"` +} + +// OrderInfo represents information about an order. +type OrderInfo struct { + // Name user name + // + // optional + Name string `json:"name,omitempty"` + // PhoneNumber user's phone number + // + // optional + PhoneNumber string `json:"phone_number,omitempty"` + // Email user email + // + // optional + Email string `json:"email,omitempty"` + // ShippingAddress user shipping address + // + // optional + ShippingAddress *ShippingAddress `json:"shipping_address,omitempty"` +} + +// ShippingOption represents one shipping option. +type ShippingOption struct { + // ID shipping option identifier + ID string `json:"id"` + // Title option title + Title string `json:"title"` + // Prices list of price portions + Prices []LabeledPrice `json:"prices"` +} + +// SuccessfulPayment contains basic information about a successful payment. +type SuccessfulPayment struct { + // Currency three-letter ISO 4217 currency code + // (see https://core.telegram.org/bots/payments#supported-currencies) + Currency string `json:"currency"` + // TotalAmount total price 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). + TotalAmount int `json:"total_amount"` + // InvoicePayload bot specified invoice payload + InvoicePayload string `json:"invoice_payload"` + // ShippingOptionID identifier of the shipping option chosen by the user + // + // optional + ShippingOptionID string `json:"shipping_option_id,omitempty"` + // OrderInfo order info provided by the user + // + // optional + OrderInfo *OrderInfo `json:"order_info,omitempty"` + // TelegramPaymentChargeID telegram payment identifier + TelegramPaymentChargeID string `json:"telegram_payment_charge_id"` + // ProviderPaymentChargeID provider payment identifier + ProviderPaymentChargeID string `json:"provider_payment_charge_id"` +} + +// ShippingQuery contains information about an incoming shipping query. +type ShippingQuery struct { + // ID unique query identifier + ID string `json:"id"` + // From user who sent the query + From *User `json:"from"` + // InvoicePayload bot specified invoice payload + InvoicePayload string `json:"invoice_payload"` + // ShippingAddress user specified shipping address + ShippingAddress *ShippingAddress `json:"shipping_address"` +} + +// PreCheckoutQuery contains information about an incoming pre-checkout query. +type PreCheckoutQuery struct { + // ID unique query identifier + ID string `json:"id"` + // From user who sent the query + From *User `json:"from"` + // Currency three-letter ISO 4217 currency code + // // (see https://core.telegram.org/bots/payments#supported-currencies) + Currency string `json:"currency"` + // TotalAmount total price 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). + TotalAmount int `json:"total_amount"` + // InvoicePayload bot specified invoice payload + InvoicePayload string `json:"invoice_payload"` + // ShippingOptionID identifier of the shipping option chosen by the user + // + // optional + ShippingOptionID string `json:"shipping_option_id,omitempty"` + // OrderInfo order info provided by the user + // + // optional + OrderInfo *OrderInfo `json:"order_info,omitempty"` +} |