copy from 537c005
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
.idea/
|
||||||
|
coverage.out
|
||||||
|
tmp/
|
||||||
|
book/
|
||||||
+21
@@ -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.
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
# Golang bindings for the Telegram Bot API
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/go-telegram-bot-api/telegram-bot-api/v5)
|
||||||
|
[](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.
|
||||||
@@ -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"
|
||||||
@@ -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"
|
||||||
|
"io/ioutil"
|
||||||
|
"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 := ioutil.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) {
|
||||||
|
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
|
||||||
|
close(ch)
|
||||||
|
}(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)
|
||||||
|
}
|
||||||
+1051
File diff suppressed because it is too large
Load Diff
+2568
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
|||||||
|
# Summary
|
||||||
|
|
||||||
|
- [Getting Started](./getting-started/README.md)
|
||||||
|
- [Library Structure](./getting-started/library-structure.md)
|
||||||
|
- [Files](./getting-started/files.md)
|
||||||
|
- [Important Notes](./getting-started/important-notes.md)
|
||||||
|
- [Examples](./examples/README.md)
|
||||||
|
- [Command Handling](./examples/command-handling.md)
|
||||||
|
- [Keyboard](./examples/keyboard.md)
|
||||||
|
- [Inline Keyboard](./examples/inline-keyboard.md)
|
||||||
|
- [Change Log](./changelog.md)
|
||||||
|
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
- [Internals](./internals/README.md)
|
||||||
|
- [Adding Endpoints](./internals/adding-endpoints.md)
|
||||||
|
- [Uploading Files](./internals/uploading-files.md)
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Change Log
|
||||||
|
|
||||||
|
## v5.4.0
|
||||||
|
|
||||||
|
- Remove all methods that return `(APIResponse, error)`.
|
||||||
|
- Use the `Request` method instead.
|
||||||
|
- For more information, see [Library Structure][library-structure].
|
||||||
|
- Remove all `New*Upload` and `New*Share` methods, replace with `New*`.
|
||||||
|
- Use different [file types][files] to specify if upload or share.
|
||||||
|
- Rename `UploadFile` to `UploadFiles`, accept `[]RequestFile` instead of a
|
||||||
|
single fieldname and file.
|
||||||
|
- Fix methods returning `APIResponse` and errors to always use pointers.
|
||||||
|
- Update user IDs to `int64` because of Bot API changes.
|
||||||
|
- Add missing Bot API features.
|
||||||
|
|
||||||
|
[library-structure]: ./getting-started/library-structure.md#methods
|
||||||
|
[files]: ./getting-started/files.md
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Examples
|
||||||
|
|
||||||
|
With a better understanding of how the library works, let's look at some more
|
||||||
|
examples showing off some of Telegram's features.
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# Command Handling
|
||||||
|
|
||||||
|
This is a simple example of changing behavior based on a provided command.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
bot, err := tgbotapi.NewBotAPI(os.Getenv("TELEGRAM_APITOKEN"))
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bot.Debug = true
|
||||||
|
|
||||||
|
log.Printf("Authorized on account %s", bot.Self.UserName)
|
||||||
|
|
||||||
|
u := tgbotapi.NewUpdate(0)
|
||||||
|
u.Timeout = 60
|
||||||
|
|
||||||
|
updates := bot.GetUpdatesChan(u)
|
||||||
|
|
||||||
|
for update := range updates {
|
||||||
|
if update.Message == nil { // ignore any non-Message updates
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !update.Message.IsCommand() { // ignore any non-command Messages
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new MessageConfig. We don't have text yet,
|
||||||
|
// so we leave it empty.
|
||||||
|
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "")
|
||||||
|
|
||||||
|
// Extract the command from the Message.
|
||||||
|
switch update.Message.Command() {
|
||||||
|
case "help":
|
||||||
|
msg.Text = "I understand /sayhi and /status."
|
||||||
|
case "sayhi":
|
||||||
|
msg.Text = "Hi :)"
|
||||||
|
case "status":
|
||||||
|
msg.Text = "I'm ok."
|
||||||
|
default:
|
||||||
|
msg.Text = "I don't know that command"
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := bot.Send(msg); err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# Inline Keyboard
|
||||||
|
|
||||||
|
This bot waits for you to send it the message "open" before sending you an
|
||||||
|
inline keyboard containing a URL and some numbers. When a number is clicked, it
|
||||||
|
sends you a message with your selected number.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
var numericKeyboard = tgbotapi.NewInlineKeyboardMarkup(
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonURL("1.com", "http://1.com"),
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("2", "2"),
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("3", "3"),
|
||||||
|
),
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("4", "4"),
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("5", "5"),
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("6", "6"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
bot, err := tgbotapi.NewBotAPI(os.Getenv("TELEGRAM_APITOKEN"))
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bot.Debug = true
|
||||||
|
|
||||||
|
log.Printf("Authorized on account %s", bot.Self.UserName)
|
||||||
|
|
||||||
|
u := tgbotapi.NewUpdate(0)
|
||||||
|
u.Timeout = 60
|
||||||
|
|
||||||
|
updates := bot.GetUpdatesChan(u)
|
||||||
|
|
||||||
|
// Loop through each update.
|
||||||
|
for update := range updates {
|
||||||
|
// Check if we've gotten a message update.
|
||||||
|
if update.Message != nil {
|
||||||
|
// Construct a new message from the given chat ID and containing
|
||||||
|
// the text that we received.
|
||||||
|
msg := tgbotapi.NewMessage(update.Message.Chat.ID, update.Message.Text)
|
||||||
|
|
||||||
|
// If the message was open, add a copy of our numeric keyboard.
|
||||||
|
switch update.Message.Text {
|
||||||
|
case "open":
|
||||||
|
msg.ReplyMarkup = numericKeyboard
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the message.
|
||||||
|
if _, err = bot.Send(msg); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
} else if update.CallbackQuery != nil {
|
||||||
|
// Respond to the callback query, telling Telegram to show the user
|
||||||
|
// a message with the data received.
|
||||||
|
callback := tgbotapi.NewCallback(update.CallbackQuery.ID, update.CallbackQuery.Data)
|
||||||
|
if _, err := bot.Request(callback); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// And finally, send a message containing the data received.
|
||||||
|
msg := tgbotapi.NewMessage(update.CallbackQuery.Message.Chat.ID, update.CallbackQuery.Data)
|
||||||
|
if _, err := bot.Send(msg); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# Keyboard
|
||||||
|
|
||||||
|
This bot shows a numeric keyboard when you send a "open" message and hides it
|
||||||
|
when you send "close" message.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
var numericKeyboard = tgbotapi.NewReplyKeyboard(
|
||||||
|
tgbotapi.NewKeyboardButtonRow(
|
||||||
|
tgbotapi.NewKeyboardButton("1"),
|
||||||
|
tgbotapi.NewKeyboardButton("2"),
|
||||||
|
tgbotapi.NewKeyboardButton("3"),
|
||||||
|
),
|
||||||
|
tgbotapi.NewKeyboardButtonRow(
|
||||||
|
tgbotapi.NewKeyboardButton("4"),
|
||||||
|
tgbotapi.NewKeyboardButton("5"),
|
||||||
|
tgbotapi.NewKeyboardButton("6"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
bot, err := tgbotapi.NewBotAPI(os.Getenv("TELEGRAM_APITOKEN"))
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bot.Debug = true
|
||||||
|
|
||||||
|
log.Printf("Authorized on account %s", bot.Self.UserName)
|
||||||
|
|
||||||
|
u := tgbotapi.NewUpdate(0)
|
||||||
|
u.Timeout = 60
|
||||||
|
|
||||||
|
updates := bot.GetUpdatesChan(u)
|
||||||
|
|
||||||
|
for update := range updates {
|
||||||
|
if update.Message == nil { // ignore non-Message updates
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := tgbotapi.NewMessage(update.Message.Chat.ID, update.Message.Text)
|
||||||
|
|
||||||
|
switch update.Message.Text {
|
||||||
|
case "open":
|
||||||
|
msg.ReplyMarkup = numericKeyboard
|
||||||
|
case "close":
|
||||||
|
msg.ReplyMarkup = tgbotapi.NewRemoveKeyboard(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := bot.Send(msg); err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
# Getting Started
|
||||||
|
|
||||||
|
This library is designed as a simple wrapper around the Telegram Bot API.
|
||||||
|
It's encouraged to read [Telegram's docs][telegram-docs] first to get an
|
||||||
|
understanding of what Bots are capable of doing. They also provide some good
|
||||||
|
approaches to solve common problems.
|
||||||
|
|
||||||
|
[telegram-docs]: https://core.telegram.org/bots
|
||||||
|
|
||||||
|
## Installing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get -u github.com/go-telegram-bot-api/telegram-bot-api/v5
|
||||||
|
```
|
||||||
|
|
||||||
|
## A Simple Bot
|
||||||
|
|
||||||
|
To walk through the basics, let's create a simple echo bot that replies to your
|
||||||
|
messages repeating what you said. Make sure you get an API token from
|
||||||
|
[@Botfather][botfather] before continuing.
|
||||||
|
|
||||||
|
Let's start by constructing a new [BotAPI][bot-api-docs].
|
||||||
|
|
||||||
|
[botfather]: https://t.me/Botfather
|
||||||
|
[bot-api-docs]: https://pkg.go.dev/github.com/go-telegram-bot-api/telegram-bot-api/v5?tab=doc#BotAPI
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
bot, err := tgbotapi.NewBotAPI(os.Getenv("TELEGRAM_APITOKEN"))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bot.Debug = true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Instead of typing the API token directly into the file, we're using
|
||||||
|
environment variables. This makes it easy to configure our Bot to use the right
|
||||||
|
account and prevents us from leaking our real token into the world. Anyone with
|
||||||
|
your token can send and receive messages from your Bot!
|
||||||
|
|
||||||
|
We've also set `bot.Debug = true` in order to get more information about the
|
||||||
|
requests being sent to Telegram. If you run the example above, you'll see
|
||||||
|
information about a request to the [`getMe`][get-me] endpoint. The library
|
||||||
|
automatically calls this to ensure your token is working as expected. It also
|
||||||
|
fills in the `Self` field in your `BotAPI` struct with information about the
|
||||||
|
Bot.
|
||||||
|
|
||||||
|
Now that we've connected to Telegram, let's start getting updates and doing
|
||||||
|
things. We can add this code in right after the line enabling debug mode.
|
||||||
|
|
||||||
|
[get-me]: https://core.telegram.org/bots/api#getme
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Create a new UpdateConfig struct with an offset of 0. Offsets are used
|
||||||
|
// to make sure Telegram knows we've handled previous values and we don't
|
||||||
|
// need them repeated.
|
||||||
|
updateConfig := tgbotapi.NewUpdate(0)
|
||||||
|
|
||||||
|
// Tell Telegram we should wait up to 30 seconds on each request for an
|
||||||
|
// update. This way we can get information just as quickly as making many
|
||||||
|
// frequent requests without having to send nearly as many.
|
||||||
|
updateConfig.Timeout = 30
|
||||||
|
|
||||||
|
// Start polling Telegram for updates.
|
||||||
|
updates := bot.GetUpdatesChan(updateConfig)
|
||||||
|
|
||||||
|
// Let's go through each update that we're getting from Telegram.
|
||||||
|
for update := range updates {
|
||||||
|
// Telegram can send many types of updates depending on what your Bot
|
||||||
|
// is up to. We only want to look at messages for now, so we can
|
||||||
|
// discard any other updates.
|
||||||
|
if update.Message == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that we know we've gotten a new message, we can construct a
|
||||||
|
// reply! We'll take the Chat ID and Text from the incoming message
|
||||||
|
// and use it to create a new message.
|
||||||
|
msg := tgbotapi.NewMessage(update.Message.Chat.ID, update.Message.Text)
|
||||||
|
// We'll also say that this message is a reply to the previous message.
|
||||||
|
// For any other specifications than Chat ID or Text, you'll need to
|
||||||
|
// set fields on the `MessageConfig`.
|
||||||
|
msg.ReplyToMessageID = update.Message.MessageID
|
||||||
|
|
||||||
|
// Okay, we're sending our message off! We don't care about the message
|
||||||
|
// we just sent, so we'll discard it.
|
||||||
|
if _, err := bot.Send(msg); err != nil {
|
||||||
|
// Note that panics are a bad way to handle errors. Telegram can
|
||||||
|
// have service outages or network errors, you should retry sending
|
||||||
|
// messages or more gracefully handle failures.
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Congradulations! You've made your very own bot!
|
||||||
|
|
||||||
|
Now that you've got some of the basics down, we can start talking about how the
|
||||||
|
library is structured and more advanced features.
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# Files
|
||||||
|
|
||||||
|
Telegram supports specifying files in many different formats. In order to
|
||||||
|
accommodate them all, there are multiple structs and type aliases required.
|
||||||
|
|
||||||
|
All of these types implement the `RequestFileData` interface.
|
||||||
|
|
||||||
|
| Type | Description |
|
||||||
|
| ------------ | ------------------------------------------------------------------------- |
|
||||||
|
| `FilePath` | A local path to a file |
|
||||||
|
| `FileID` | Existing file ID on Telegram's servers |
|
||||||
|
| `FileURL` | URL to file, must be served with expected MIME type |
|
||||||
|
| `FileReader` | Use an `io.Reader` to provide a file. Lazily read to save memory. |
|
||||||
|
| `FileBytes` | `[]byte` containing file data. Prefer to use `FileReader` to save memory. |
|
||||||
|
|
||||||
|
## `FilePath`
|
||||||
|
|
||||||
|
A path to a local file.
|
||||||
|
|
||||||
|
```go
|
||||||
|
file := tgbotapi.FilePath("tests/image.jpg")
|
||||||
|
```
|
||||||
|
|
||||||
|
## `FileID`
|
||||||
|
|
||||||
|
An ID previously uploaded to Telegram. IDs may only be reused by the same bot
|
||||||
|
that received them. Additionally, thumbnail IDs cannot be reused.
|
||||||
|
|
||||||
|
```go
|
||||||
|
file := tgbotapi.FileID("AgACAgIAAxkDAALesF8dCjAAAa_…")
|
||||||
|
```
|
||||||
|
|
||||||
|
## `FileURL`
|
||||||
|
|
||||||
|
A URL to an existing resource. It must be served with a correct MIME type to
|
||||||
|
work as expected.
|
||||||
|
|
||||||
|
```go
|
||||||
|
file := tgbotapi.FileURL("https://i.imgur.com/unQLJIb.jpg")
|
||||||
|
```
|
||||||
|
|
||||||
|
## `FileReader`
|
||||||
|
|
||||||
|
Use an `io.Reader` to provide file contents as needed. Requires a filename for
|
||||||
|
the virtual file.
|
||||||
|
|
||||||
|
```go
|
||||||
|
var reader io.Reader
|
||||||
|
|
||||||
|
file := tgbotapi.FileReader{
|
||||||
|
Name: "image.jpg",
|
||||||
|
Reader: reader,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## `FileBytes`
|
||||||
|
|
||||||
|
Use a `[]byte` to provide file contents. Generally try to avoid this as it
|
||||||
|
results in high memory usage. Also requires a filename for the virtual file.
|
||||||
|
|
||||||
|
```go
|
||||||
|
var data []byte
|
||||||
|
|
||||||
|
file := tgbotapi.FileBytes{
|
||||||
|
Name: "image.jpg",
|
||||||
|
Bytes: data,
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# Important Notes
|
||||||
|
|
||||||
|
The Telegram Bot API has a few potentially unanticipated behaviors. Here are a
|
||||||
|
few of them. If any behavior was surprising to you, please feel free to open a
|
||||||
|
pull request!
|
||||||
|
|
||||||
|
## Callback Queries
|
||||||
|
|
||||||
|
- Every callback query must be answered, even if there is nothing to display to
|
||||||
|
the user. Failure to do so will show a loading icon on the keyboard until the
|
||||||
|
operation times out.
|
||||||
|
|
||||||
|
## ChatMemberUpdated
|
||||||
|
|
||||||
|
- In order to receive `ChatMember` updates, you must explicitly add
|
||||||
|
`UpdateTypeChatMember` to your `AllowedUpdates` when getting updates or
|
||||||
|
setting your webhook.
|
||||||
|
|
||||||
|
## Entities use UTF16
|
||||||
|
|
||||||
|
- When extracting text entities using offsets and lengths, characters can appear
|
||||||
|
to be in incorrect positions. This is because Telegram uses UTF16 lengths
|
||||||
|
while Golang uses UTF8. It's possible to convert between the two, see
|
||||||
|
[issue #231][issue-231] for more details.
|
||||||
|
|
||||||
|
[issue-231]: https://github.com/go-telegram-bot-api/telegram-bot-api/issues/231
|
||||||
|
|
||||||
|
## GetUpdatesChan
|
||||||
|
|
||||||
|
- This method is very basic and likely unsuitable for production use. Consider
|
||||||
|
creating your own implementation instead, as it's very simple to replicate.
|
||||||
|
- This method only allows your bot to process one update at a time. You can
|
||||||
|
spawn goroutines to handle updates concurrently or switch to webhooks instead.
|
||||||
|
Webhooks are suggested for high traffic bots.
|
||||||
|
|
||||||
|
## Nil Updates
|
||||||
|
|
||||||
|
- At most one of the fields in an `Update` will be set to a non-nil value. When
|
||||||
|
evaluating updates, you must make sure you check that the field is not nil
|
||||||
|
before trying to access any of it's fields.
|
||||||
|
|
||||||
|
## Privacy Mode
|
||||||
|
|
||||||
|
- By default, bots only get updates directly addressed to them. If you need to
|
||||||
|
get all messages, you must disable privacy mode with Botfather. Bots already
|
||||||
|
added to groups will need to be removed and readded for the changes to take
|
||||||
|
effect. You can read more on the [Telegram Bot API docs][api-docs].
|
||||||
|
|
||||||
|
[api-docs]: https://core.telegram.org/bots/faq#what-messages-will-my-bot-get
|
||||||
|
|
||||||
|
## User and Chat ID size
|
||||||
|
|
||||||
|
- These types require up to 52 significant bits to store correctly, making a
|
||||||
|
64-bit integer type required in most languages. They are already `int64` types
|
||||||
|
in this library, but make sure you use correct types when saving them to a
|
||||||
|
database or passing them to another language.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# Library Structure
|
||||||
|
|
||||||
|
This library is generally broken into three components you need to understand.
|
||||||
|
|
||||||
|
## Configs
|
||||||
|
|
||||||
|
Configs are collections of fields related to a single request. For example, if
|
||||||
|
one wanted to use the `sendMessage` endpoint, you could use the `MessageConfig`
|
||||||
|
struct to configure the request. There is a one-to-one relationship between
|
||||||
|
Telegram endpoints and configs. They generally have the naming pattern of
|
||||||
|
removing the `send` prefix and they all end with the `Config` suffix. They
|
||||||
|
generally implement the `Chattable` interface. If they can send files, they
|
||||||
|
implement the `Fileable` interface.
|
||||||
|
|
||||||
|
## Helpers
|
||||||
|
|
||||||
|
Helpers are easier ways of constructing common Configs. Instead of having to
|
||||||
|
create a `MessageConfig` struct and remember to set the `ChatID` and `Text`,
|
||||||
|
you can use the `NewMessage` helper method. It takes the two required parameters
|
||||||
|
for the request to succeed. You can then set fields on the resulting
|
||||||
|
`MessageConfig` after it's creation. They are generally named the same as
|
||||||
|
method names except with `send` replaced with `New`.
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
Methods are used to send Configs after they are constructed. Generally,
|
||||||
|
`Request` is the lowest level method you'll have to call. It accepts a
|
||||||
|
`Chattable` parameter and knows how to upload files if needed. It returns an
|
||||||
|
`APIResponse`, the most general return type from the Bot API. This method is
|
||||||
|
called for any endpoint that doesn't have a more specific return type. For
|
||||||
|
example, `setWebhook` only returns `true` or an error. Other methods may have
|
||||||
|
more specific return types. The `getFile` endpoint returns a `File`. Almost
|
||||||
|
every other method returns a `Message`, which you can use `Send` to obtain.
|
||||||
|
|
||||||
|
There's lower level methods such as `MakeRequest` which require an endpoint and
|
||||||
|
parameters instead of accepting configs. These are primarily used internally.
|
||||||
|
If you find yourself having to use them, please open an issue.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Internals
|
||||||
|
|
||||||
|
If you want to contribute to the project, here's some more information about
|
||||||
|
the internal structure of the library.
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
# Adding Endpoints
|
||||||
|
|
||||||
|
This is mostly useful if you've managed to catch a new Telegram Bot API update
|
||||||
|
before the library can get updated. It's also a great source of information
|
||||||
|
about how the types work internally.
|
||||||
|
|
||||||
|
## Creating the Config
|
||||||
|
|
||||||
|
The first step in adding a new endpoint is to create a new Config type for it.
|
||||||
|
These belong in `configs.go`.
|
||||||
|
|
||||||
|
Let's try and add the `deleteMessage` endpoint. We can see it requires two
|
||||||
|
fields; `chat_id` and `message_id`. We can create a struct for these.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type DeleteMessageConfig struct {
|
||||||
|
ChatID ???
|
||||||
|
MessageID int
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
What type should `ChatID` be? Telegram allows specifying numeric chat IDs or
|
||||||
|
channel usernames. Golang doesn't have union types, and interfaces are entirely
|
||||||
|
untyped. This library solves this by adding two fields, a `ChatID` and a
|
||||||
|
`ChannelUsername`. We can now write the struct as follows.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type DeleteMessageConfig struct {
|
||||||
|
ChannelUsername string
|
||||||
|
ChatID int64
|
||||||
|
MessageID int
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that `ChatID` is an `int64`. Telegram chat IDs can be greater than 32 bits.
|
||||||
|
|
||||||
|
Okay, we now have our struct. But we can't send it yet. It doesn't implement
|
||||||
|
`Chattable` so it won't work with `Request` or `Send`.
|
||||||
|
|
||||||
|
### Making it `Chattable`
|
||||||
|
|
||||||
|
We can see that `Chattable` only requires a few methods.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Chattable interface {
|
||||||
|
params() (Params, error)
|
||||||
|
method() string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`params` is the fields associated with the request. `method` is the endpoint
|
||||||
|
that this Config is associated with.
|
||||||
|
|
||||||
|
Implementing the `method` is easy, so let's start with that.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (config DeleteMessageConfig) method() string {
|
||||||
|
return "deleteMessage"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we have to add the `params`. The `Params` type is an alias for
|
||||||
|
`map[string]string`. Telegram expects only a single field for `chat_id`, so we
|
||||||
|
have to determine what data to send.
|
||||||
|
|
||||||
|
We could use an if statement to determine which field to get the value from.
|
||||||
|
However, as this is a relatively common operation, there's helper methods for
|
||||||
|
`Params`. We can use the `AddFirstValid` method to go through each possible
|
||||||
|
value and stop when it discovers a valid one. Before writing your own Config,
|
||||||
|
it's worth taking a look through `params.go` to see what other helpers exist.
|
||||||
|
|
||||||
|
Now we can take a look at what a completed `params` method looks like.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (config DeleteMessageConfig) params() (Params, error) {
|
||||||
|
params := make(Params)
|
||||||
|
|
||||||
|
params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
|
||||||
|
params.AddNonZero("message_id", config.MessageID)
|
||||||
|
|
||||||
|
return params, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Uploading Files
|
||||||
|
|
||||||
|
Let's imagine that for some reason deleting a message requires a document to be
|
||||||
|
uploaded and an optional thumbnail for that document. To add file upload
|
||||||
|
support we need to implement `Fileable`. This only requires one additional
|
||||||
|
method.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Fileable interface {
|
||||||
|
Chattable
|
||||||
|
files() []RequestFile
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
First, let's add some fields to store our files in. Most of the standard Configs
|
||||||
|
have similar fields for their files.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
type DeleteMessageConfig struct {
|
||||||
|
ChannelUsername string
|
||||||
|
ChatID int64
|
||||||
|
MessageID int
|
||||||
|
+ Delete RequestFileData
|
||||||
|
+ Thumb RequestFileData
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Adding another method is pretty simple. We'll always add a file named `delete`
|
||||||
|
and add the `thumb` file if we have one.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (config DeleteMessageConfig) files() []RequestFile {
|
||||||
|
files := []RequestFile{{
|
||||||
|
Name: "delete",
|
||||||
|
Data: config.Delete,
|
||||||
|
}}
|
||||||
|
|
||||||
|
if config.Thumb != nil {
|
||||||
|
files = append(files, RequestFile{
|
||||||
|
Name: "thumb",
|
||||||
|
Data: config.Thumb,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And now our files will upload! It will transparently handle uploads whether File
|
||||||
|
is a `FilePath`, `FileURL`, `FileBytes`, `FileReader`, or `FileID`.
|
||||||
|
|
||||||
|
### Base Configs
|
||||||
|
|
||||||
|
Certain Configs have repeated elements. For example, many of the items sent to a
|
||||||
|
chat have `ChatID` or `ChannelUsername` fields, along with `ReplyToMessageID`,
|
||||||
|
`ReplyMarkup`, and `DisableNotification`. Instead of implementing all of this
|
||||||
|
code for each item, there's a `BaseChat` that handles it for your Config.
|
||||||
|
Simply embed it in your struct to get all of those fields.
|
||||||
|
|
||||||
|
There's only a few fields required for the `MessageConfig` struct after
|
||||||
|
embedding the `BaseChat` struct.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type MessageConfig struct {
|
||||||
|
BaseChat
|
||||||
|
Text string
|
||||||
|
ParseMode string
|
||||||
|
DisableWebPagePreview bool
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It also inherits the `params` method from `BaseChat`. This allows you to call
|
||||||
|
it, then you only have to add your new fields.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (config MessageConfig) params() (Params, error) {
|
||||||
|
params, err := config.BaseChat.params()
|
||||||
|
if err != nil {
|
||||||
|
return params, err
|
||||||
|
}
|
||||||
|
|
||||||
|
params.AddNonEmpty("text", config.Text)
|
||||||
|
// Add your other fields
|
||||||
|
|
||||||
|
return params, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Similarly, there's a `BaseFile` struct for adding an associated file and
|
||||||
|
`BaseEdit` struct for editing messages.
|
||||||
|
|
||||||
|
## Making it Friendly
|
||||||
|
|
||||||
|
After we've got a Config type, we'll want to make it more user-friendly. We can
|
||||||
|
do this by adding a new helper to `helpers.go`. These are functions that take
|
||||||
|
in the required data for the request to succeed and populate a Config.
|
||||||
|
|
||||||
|
Telegram only requires two fields to call `deleteMessage`, so this will be fast.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func NewDeleteMessage(chatID int64, messageID int) DeleteMessageConfig {
|
||||||
|
return DeleteMessageConfig{
|
||||||
|
ChatID: chatID,
|
||||||
|
MessageID: messageID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Sometimes it makes sense to add more helpers if there's methods where you have
|
||||||
|
to set exactly one field. You can also add helpers that accept a `username`
|
||||||
|
string for channels if it's a common operation.
|
||||||
|
|
||||||
|
And that's it! You've added a new method.
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# Uploading Files
|
||||||
|
|
||||||
|
To make files work as expected, there's a lot going on behind the scenes. Make
|
||||||
|
sure to read through the [Files](../getting-started/files.md) section in
|
||||||
|
Getting Started first as we'll be building on that information.
|
||||||
|
|
||||||
|
This section only talks about file uploading. For non-uploaded files such as
|
||||||
|
URLs and file IDs, you just need to pass a string.
|
||||||
|
|
||||||
|
## Fields
|
||||||
|
|
||||||
|
Let's start by talking about how the library represents files as part of a
|
||||||
|
Config.
|
||||||
|
|
||||||
|
### Static Fields
|
||||||
|
|
||||||
|
Most endpoints use static file fields. For example, `sendPhoto` expects a single
|
||||||
|
file named `photo`. All we have to do is set that single field with the correct
|
||||||
|
value (either a string or multipart file). Methods like `sendDocument` take two
|
||||||
|
file uploads, a `document` and a `thumb`. These are pretty straightforward.
|
||||||
|
|
||||||
|
Remembering that the `Fileable` interface only requires one method, let's
|
||||||
|
implement it for `DocumentConfig`.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (config DocumentConfig) files() []RequestFile {
|
||||||
|
// We can have multiple files, so we'll create an array. We also know that
|
||||||
|
// there always is a document file, so initialize the array with that.
|
||||||
|
files := []RequestFile{{
|
||||||
|
Name: "document",
|
||||||
|
Data: config.File,
|
||||||
|
}}
|
||||||
|
|
||||||
|
// We'll only add a file if we have one.
|
||||||
|
if config.Thumb != nil {
|
||||||
|
files = append(files, RequestFile{
|
||||||
|
Name: "thumb",
|
||||||
|
Data: config.Thumb,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Telegram also supports the `attach://` syntax (discussed more later) for
|
||||||
|
thumbnails, but there's no reason to make things more complicated.
|
||||||
|
|
||||||
|
### Dynamic Fields
|
||||||
|
|
||||||
|
Of course, not everything can be so simple. Methods like `sendMediaGroup`
|
||||||
|
can accept many files, and each file can have custom markup. Using a static
|
||||||
|
field isn't possible because we need to specify which field is attached to each
|
||||||
|
item. Telegram introduced the `attach://` syntax for this.
|
||||||
|
|
||||||
|
Let's follow through creating a new media group with string and file uploads.
|
||||||
|
|
||||||
|
First, we start by creating some `InputMediaPhoto`.
|
||||||
|
|
||||||
|
```go
|
||||||
|
photo := tgbotapi.NewInputMediaPhoto(tgbotapi.FilePath("tests/image.jpg"))
|
||||||
|
url := tgbotapi.NewInputMediaPhoto(tgbotapi.FileURL("https://i.imgur.com/unQLJIb.jpg"))
|
||||||
|
```
|
||||||
|
|
||||||
|
This created a new `InputMediaPhoto` struct, with a type of `photo` and the
|
||||||
|
media interface that we specified.
|
||||||
|
|
||||||
|
We'll now create our media group with the photo and URL.
|
||||||
|
|
||||||
|
```go
|
||||||
|
mediaGroup := NewMediaGroup(ChatID, []interface{}{
|
||||||
|
photo,
|
||||||
|
url,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
A `MediaGroupConfig` stores all of the media in an array of interfaces. We now
|
||||||
|
have all of the data we need to upload, but how do we figure out field names for
|
||||||
|
uploads? We didn't specify `attach://unique-file` anywhere.
|
||||||
|
|
||||||
|
When the library goes to upload the files, it looks at the `params` and `files`
|
||||||
|
for the Config. The params are generated by transforming the file into a value
|
||||||
|
more suitable for uploading, file IDs and URLs are untouched but uploaded types
|
||||||
|
are all changed into `attach://file-%d`. When collecting a list of files to
|
||||||
|
upload, it names them the same way. This creates a nearly transparent way of
|
||||||
|
handling multiple files in the background without the user having to consider
|
||||||
|
what's going on.
|
||||||
+927
@@ -0,0 +1,927 @@
|
|||||||
|
package tgbotapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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{
|
||||||
|
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 replymarkup 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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}
|
||||||
|
}
|
||||||
+236
@@ -0,0 +1,236 @@
|
|||||||
|
package tgbotapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewWebhook(t *testing.T) {
|
||||||
|
result, err := NewWebhook("https://example.com/token")
|
||||||
|
|
||||||
|
if err != nil ||
|
||||||
|
result.URL.String() != "https://example.com/token" ||
|
||||||
|
result.Certificate != interface{}(nil) ||
|
||||||
|
result.MaxConnections != 0 ||
|
||||||
|
len(result.AllowedUpdates) != 0 {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewWebhookWithCert(t *testing.T) {
|
||||||
|
exampleFile := FileID("123")
|
||||||
|
result, err := NewWebhookWithCert("https://example.com/token", exampleFile)
|
||||||
|
|
||||||
|
if err != nil ||
|
||||||
|
result.URL.String() != "https://example.com/token" ||
|
||||||
|
result.Certificate != exampleFile ||
|
||||||
|
result.MaxConnections != 0 ||
|
||||||
|
len(result.AllowedUpdates) != 0 {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewInlineQueryResultArticle(t *testing.T) {
|
||||||
|
result := NewInlineQueryResultArticle("id", "title", "message")
|
||||||
|
|
||||||
|
if result.Type != "article" ||
|
||||||
|
result.ID != "id" ||
|
||||||
|
result.Title != "title" ||
|
||||||
|
result.InputMessageContent.(InputTextMessageContent).Text != "message" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewInlineQueryResultArticleMarkdown(t *testing.T) {
|
||||||
|
result := NewInlineQueryResultArticleMarkdown("id", "title", "*message*")
|
||||||
|
|
||||||
|
if result.Type != "article" ||
|
||||||
|
result.ID != "id" ||
|
||||||
|
result.Title != "title" ||
|
||||||
|
result.InputMessageContent.(InputTextMessageContent).Text != "*message*" ||
|
||||||
|
result.InputMessageContent.(InputTextMessageContent).ParseMode != "Markdown" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewInlineQueryResultArticleHTML(t *testing.T) {
|
||||||
|
result := NewInlineQueryResultArticleHTML("id", "title", "<b>message</b>")
|
||||||
|
|
||||||
|
if result.Type != "article" ||
|
||||||
|
result.ID != "id" ||
|
||||||
|
result.Title != "title" ||
|
||||||
|
result.InputMessageContent.(InputTextMessageContent).Text != "<b>message</b>" ||
|
||||||
|
result.InputMessageContent.(InputTextMessageContent).ParseMode != "HTML" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewInlineQueryResultGIF(t *testing.T) {
|
||||||
|
result := NewInlineQueryResultGIF("id", "google.com")
|
||||||
|
|
||||||
|
if result.Type != "gif" ||
|
||||||
|
result.ID != "id" ||
|
||||||
|
result.URL != "google.com" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewInlineQueryResultMPEG4GIF(t *testing.T) {
|
||||||
|
result := NewInlineQueryResultMPEG4GIF("id", "google.com")
|
||||||
|
|
||||||
|
if result.Type != "mpeg4_gif" ||
|
||||||
|
result.ID != "id" ||
|
||||||
|
result.URL != "google.com" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewInlineQueryResultPhoto(t *testing.T) {
|
||||||
|
result := NewInlineQueryResultPhoto("id", "google.com")
|
||||||
|
|
||||||
|
if result.Type != "photo" ||
|
||||||
|
result.ID != "id" ||
|
||||||
|
result.URL != "google.com" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewInlineQueryResultPhotoWithThumb(t *testing.T) {
|
||||||
|
result := NewInlineQueryResultPhotoWithThumb("id", "google.com", "thumb.com")
|
||||||
|
|
||||||
|
if result.Type != "photo" ||
|
||||||
|
result.ID != "id" ||
|
||||||
|
result.URL != "google.com" ||
|
||||||
|
result.ThumbURL != "thumb.com" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewInlineQueryResultVideo(t *testing.T) {
|
||||||
|
result := NewInlineQueryResultVideo("id", "google.com")
|
||||||
|
|
||||||
|
if result.Type != "video" ||
|
||||||
|
result.ID != "id" ||
|
||||||
|
result.URL != "google.com" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewInlineQueryResultAudio(t *testing.T) {
|
||||||
|
result := NewInlineQueryResultAudio("id", "google.com", "title")
|
||||||
|
|
||||||
|
if result.Type != "audio" ||
|
||||||
|
result.ID != "id" ||
|
||||||
|
result.URL != "google.com" ||
|
||||||
|
result.Title != "title" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewInlineQueryResultVoice(t *testing.T) {
|
||||||
|
result := NewInlineQueryResultVoice("id", "google.com", "title")
|
||||||
|
|
||||||
|
if result.Type != "voice" ||
|
||||||
|
result.ID != "id" ||
|
||||||
|
result.URL != "google.com" ||
|
||||||
|
result.Title != "title" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewInlineQueryResultDocument(t *testing.T) {
|
||||||
|
result := NewInlineQueryResultDocument("id", "google.com", "title", "mime/type")
|
||||||
|
|
||||||
|
if result.Type != "document" ||
|
||||||
|
result.ID != "id" ||
|
||||||
|
result.URL != "google.com" ||
|
||||||
|
result.Title != "title" ||
|
||||||
|
result.MimeType != "mime/type" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewInlineQueryResultLocation(t *testing.T) {
|
||||||
|
result := NewInlineQueryResultLocation("id", "name", 40, 50)
|
||||||
|
|
||||||
|
if result.Type != "location" ||
|
||||||
|
result.ID != "id" ||
|
||||||
|
result.Title != "name" ||
|
||||||
|
result.Latitude != 40 ||
|
||||||
|
result.Longitude != 50 {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewInlineKeyboardButtonLoginURL(t *testing.T) {
|
||||||
|
result := NewInlineKeyboardButtonLoginURL("text", LoginURL{
|
||||||
|
URL: "url",
|
||||||
|
ForwardText: "ForwardText",
|
||||||
|
BotUsername: "username",
|
||||||
|
RequestWriteAccess: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
if result.Text != "text" ||
|
||||||
|
result.LoginURL.URL != "url" ||
|
||||||
|
result.LoginURL.ForwardText != "ForwardText" ||
|
||||||
|
result.LoginURL.BotUsername != "username" ||
|
||||||
|
result.LoginURL.RequestWriteAccess != false {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewEditMessageText(t *testing.T) {
|
||||||
|
edit := NewEditMessageText(ChatID, ReplyToMessageID, "new text")
|
||||||
|
|
||||||
|
if edit.Text != "new text" ||
|
||||||
|
edit.BaseEdit.ChatID != ChatID ||
|
||||||
|
edit.BaseEdit.MessageID != ReplyToMessageID {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewEditMessageCaption(t *testing.T) {
|
||||||
|
edit := NewEditMessageCaption(ChatID, ReplyToMessageID, "new caption")
|
||||||
|
|
||||||
|
if edit.Caption != "new caption" ||
|
||||||
|
edit.BaseEdit.ChatID != ChatID ||
|
||||||
|
edit.BaseEdit.MessageID != ReplyToMessageID {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewEditMessageReplyMarkup(t *testing.T) {
|
||||||
|
markup := InlineKeyboardMarkup{
|
||||||
|
InlineKeyboard: [][]InlineKeyboardButton{
|
||||||
|
{
|
||||||
|
{Text: "test"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
edit := NewEditMessageReplyMarkup(ChatID, ReplyToMessageID, markup)
|
||||||
|
|
||||||
|
if edit.ReplyMarkup.InlineKeyboard[0][0].Text != "test" ||
|
||||||
|
edit.BaseEdit.ChatID != ChatID ||
|
||||||
|
edit.BaseEdit.MessageID != ReplyToMessageID {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewDice(t *testing.T) {
|
||||||
|
dice := NewDice(42)
|
||||||
|
|
||||||
|
if dice.ChatID != 42 ||
|
||||||
|
dice.Emoji != "" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewDiceWithEmoji(t *testing.T) {
|
||||||
|
dice := NewDiceWithEmoji(42, "🏀")
|
||||||
|
|
||||||
|
if dice.ChatID != 42 ||
|
||||||
|
dice.Emoji != "🏀" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package tgbotapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Params represents a set of parameters that gets passed to a request.
|
||||||
|
type Params map[string]string
|
||||||
|
|
||||||
|
// AddNonEmpty adds a value if it not an empty string.
|
||||||
|
func (p Params) AddNonEmpty(key, value string) {
|
||||||
|
if value != "" {
|
||||||
|
p[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddNonZero adds a value if it is not zero.
|
||||||
|
func (p Params) AddNonZero(key string, value int) {
|
||||||
|
if value != 0 {
|
||||||
|
p[key] = strconv.Itoa(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddNonZero64 is the same as AddNonZero except uses an int64.
|
||||||
|
func (p Params) AddNonZero64(key string, value int64) {
|
||||||
|
if value != 0 {
|
||||||
|
p[key] = strconv.FormatInt(value, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddBool adds a value of a bool if it is true.
|
||||||
|
func (p Params) AddBool(key string, value bool) {
|
||||||
|
if value {
|
||||||
|
p[key] = strconv.FormatBool(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddNonZeroFloat adds a floating point value that is not zero.
|
||||||
|
func (p Params) AddNonZeroFloat(key string, value float64) {
|
||||||
|
if value != 0 {
|
||||||
|
p[key] = strconv.FormatFloat(value, 'f', 6, 64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddInterface adds an interface if it is not 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
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package tgbotapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func assertLen(t *testing.T, params Params, l int) {
|
||||||
|
actual := len(params)
|
||||||
|
if actual != l {
|
||||||
|
t.Fatalf("Incorrect number of params, expected %d but found %d\n", l, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertEq(t *testing.T, a interface{}, b interface{}) {
|
||||||
|
if a != b {
|
||||||
|
t.Fatalf("Values did not match, a: %v, b: %v\n", a, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddNonEmpty(t *testing.T) {
|
||||||
|
params := make(Params)
|
||||||
|
params.AddNonEmpty("value", "value")
|
||||||
|
assertLen(t, params, 1)
|
||||||
|
assertEq(t, params["value"], "value")
|
||||||
|
params.AddNonEmpty("test", "")
|
||||||
|
assertLen(t, params, 1)
|
||||||
|
assertEq(t, params["test"], "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddNonZero(t *testing.T) {
|
||||||
|
params := make(Params)
|
||||||
|
params.AddNonZero("value", 1)
|
||||||
|
assertLen(t, params, 1)
|
||||||
|
assertEq(t, params["value"], "1")
|
||||||
|
params.AddNonZero("test", 0)
|
||||||
|
assertLen(t, params, 1)
|
||||||
|
assertEq(t, params["test"], "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddNonZero64(t *testing.T) {
|
||||||
|
params := make(Params)
|
||||||
|
params.AddNonZero64("value", 1)
|
||||||
|
assertLen(t, params, 1)
|
||||||
|
assertEq(t, params["value"], "1")
|
||||||
|
params.AddNonZero64("test", 0)
|
||||||
|
assertLen(t, params, 1)
|
||||||
|
assertEq(t, params["test"], "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddBool(t *testing.T) {
|
||||||
|
params := make(Params)
|
||||||
|
params.AddBool("value", true)
|
||||||
|
assertLen(t, params, 1)
|
||||||
|
assertEq(t, params["value"], "true")
|
||||||
|
params.AddBool("test", false)
|
||||||
|
assertLen(t, params, 1)
|
||||||
|
assertEq(t, params["test"], "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddNonZeroFloat(t *testing.T) {
|
||||||
|
params := make(Params)
|
||||||
|
params.AddNonZeroFloat("value", 1)
|
||||||
|
assertLen(t, params, 1)
|
||||||
|
assertEq(t, params["value"], "1.000000")
|
||||||
|
params.AddNonZeroFloat("test", 0)
|
||||||
|
assertLen(t, params, 1)
|
||||||
|
assertEq(t, params["test"], "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddInterface(t *testing.T) {
|
||||||
|
params := make(Params)
|
||||||
|
data := struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}{
|
||||||
|
Name: "test",
|
||||||
|
}
|
||||||
|
params.AddInterface("value", data)
|
||||||
|
assertLen(t, params, 1)
|
||||||
|
assertEq(t, params["value"], `{"name":"test"}`)
|
||||||
|
params.AddInterface("test", nil)
|
||||||
|
assertLen(t, params, 1)
|
||||||
|
assertEq(t, params["test"], "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddFirstValid(t *testing.T) {
|
||||||
|
params := make(Params)
|
||||||
|
params.AddFirstValid("value", 0, "", "test")
|
||||||
|
assertLen(t, params, 1)
|
||||||
|
assertEq(t, params["value"], "test")
|
||||||
|
params.AddFirstValid("value2", 3, "test")
|
||||||
|
assertLen(t, params, 2)
|
||||||
|
assertEq(t, params["value2"], "3")
|
||||||
|
}
|
||||||
+317
@@ -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"`
|
||||||
|
}
|
||||||
|
)
|
||||||
Binary file not shown.
@@ -0,0 +1,18 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC0zCCAbugAwIBAgIJAPYfllX657axMA0GCSqGSIb3DQEBCwUAMAAwHhcNMTUx
|
||||||
|
MTIxMTExMDQxWhcNMjUwODIwMTExMDQxWjAAMIIBIjANBgkqhkiG9w0BAQEFAAOC
|
||||||
|
AQ8AMIIBCgKCAQEAoMMSIIgYx8pT8Kz1O8Ukd/JVyqBQYRSo0enqEzo7295VROXq
|
||||||
|
TUthbEbdi0OczUfl4IsAWppOSRrDwEguJZ0cJ/r6IxGsbrCdQr2MjgiomYtAXKKQ
|
||||||
|
GAGL5Wls+AzcRNV0OszVJzkDNFYZzgNejyitGJSNEQMyU8r2gyPyIWP9MQKQst8y
|
||||||
|
Mg91R/7l9jwf6AWwNxykZlYZurtsQ6XsBPZpF9YOFL7vZYPhKUFiNEm+74RpojC7
|
||||||
|
Gt6nztYAUI2V/F+1uoXAr8nLpbj9SD0VSwyZLRG1uIVLBzhb0lfOIzAvJ45EKki9
|
||||||
|
nejyoXfH1U5+iMzdSAdcy3MCBhpEZwJPqhDqeQIDAQABo1AwTjAdBgNVHQ4EFgQU
|
||||||
|
JE0RLM+ohLnlDz0Qk0McCxtDK2MwHwYDVR0jBBgwFoAUJE0RLM+ohLnlDz0Qk0Mc
|
||||||
|
CxtDK2MwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAEmgME00JYuYZ
|
||||||
|
4wNaGrJskZ05ZnP+TXJusmBui9ToQ4UoykuyY5rsdGQ3SdzXPLdmd2nfMsw63iK2
|
||||||
|
D7rjcH/rmn6fRccZqN0o0SXd/EuHeIoeW1Xnnivbt71b6mcOAeNg1UsMYxnMAVl0
|
||||||
|
ywdkta8gURltagSfXoUbqlnSxn/zCwqaxxcQXA/CnunvRsFtQrwWjDBPg/BPULHX
|
||||||
|
DEh2AactGtnGqEZ5iap/VCOVnmL6iPdJ1x5UIF/gS6U96wL+GHfcs1jCvPg+GEwR
|
||||||
|
3inh9oTXG9L21ge4lbGiPUIMBjtVcB3bXuQbOfec9Cr3ZhcQeZj680BIRxD/pNpA
|
||||||
|
O/XeCfjfkw==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
@@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQCgwxIgiBjHylPw
|
||||||
|
rPU7xSR38lXKoFBhFKjR6eoTOjvb3lVE5epNS2FsRt2LQ5zNR+XgiwBamk5JGsPA
|
||||||
|
SC4lnRwn+vojEaxusJ1CvYyOCKiZi0BcopAYAYvlaWz4DNxE1XQ6zNUnOQM0VhnO
|
||||||
|
A16PKK0YlI0RAzJTyvaDI/IhY/0xApCy3zIyD3VH/uX2PB/oBbA3HKRmVhm6u2xD
|
||||||
|
pewE9mkX1g4Uvu9lg+EpQWI0Sb7vhGmiMLsa3qfO1gBQjZX8X7W6hcCvyculuP1I
|
||||||
|
PRVLDJktEbW4hUsHOFvSV84jMC8njkQqSL2d6PKhd8fVTn6IzN1IB1zLcwIGGkRn
|
||||||
|
Ak+qEOp5AgMBAAECggEBAJ/dPCJzlEjhL5XPONLmGXzZ1Gx5/VR86eBMv0O9jhb3
|
||||||
|
wk2QYO3aPxggZGD/rGcKz1L6hzCR77WM0wpb/N/Um1I6pxHGmnU8VjYvLh10CM0f
|
||||||
|
h7JWyfnFV+ubagxFJamhpkJuvKyTaldaI7EU8qxj47Xky18Wka53z6nbTgXcW8Sm
|
||||||
|
V4CJy9OHNgKJQnylX6zOAaxVngSGde3xLslLjsYK4w9b2+OkCSUST2XXdo+ZLXxl
|
||||||
|
cs0lEPFRM1Xh9/E6UrDrJMHHzio53L/W/+a8sIar1upgSY52pyD/tA7VSrAJ9nYC
|
||||||
|
RezOU81VTLfMO+TYmgZzSUQJYh0cR4yqJe+wgl4U550CgYEA1EcS6Z+PO5Pr3u2+
|
||||||
|
XevawSAal6y9ONkkdOoASC977W37nn0E1wlQo41dR6DESCJfiSMeN0KbmXj5Wnc/
|
||||||
|
ADu+73iGwC90G9Qs9sjD7KAFBJvuj0V8hxvpWRdIBBbf7rlOj3CV0iXRYjkJbyJa
|
||||||
|
cxuR0kiv4gTWmm5Cq+5ir8t1Oc8CgYEAwd+xOaDerNR481R+QmENFw+oR2EVMq3Q
|
||||||
|
B/vinLK0PemQWrh32iBcd+vhSilOSQtUm1nko1jLK8C4s8X2vZYua4m5tcK9VqCt
|
||||||
|
maCCq/ffxzsoW/GN8japnduz+qA+hKWJzW/aYR8tsOeqzjVqj4iIqPI4HuokrDi/
|
||||||
|
UD/QLgq5UTcCgYEAk2ZC0Kx15dXB7AtDq63xOTcUoAtXXRkSgohV58npEKXVGWkQ
|
||||||
|
Kk0SjG7Fvc35XWlY0z3qZk6/AuOIqfOxcHUMEPatAtgwlH5RNo+T1EQNF/U6wotq
|
||||||
|
e9q6vp026XgEyJwt29Y+giy2ZrDaRywgiFs1d0H3t0bKyXMUopQmPJFXdesCgYEA
|
||||||
|
psCxXcDpZjxGX/zPsGZrbOdxtRtisTlg0k0rp93pO8tV90HtDHeDMT54g2ItzJPr
|
||||||
|
TMev6XOpJNPZyf6+8GhpOuO2EQkT85u2VYoCeslz95gBabvFfIzZrUZYcnw76bm8
|
||||||
|
YjAP5DN+CEfq2PyG0Df+W1ojPSvlKSCSJQMOG1vr81cCgYEAkjPY5WR99uJxYBNI
|
||||||
|
OTFMSkETgDUbPXBu/E/h5Dtn79v8Moj9FvC7+q6sg9qXhrGhfK2xDev3/sTrbS/E
|
||||||
|
Gcf8UNIne3AXsoAS8MtkOwJXHkYaTIboIYgDX4LlDmbGQlIRaWgyh2POI6VtjLBT
|
||||||
|
ms6AdsdpIB6As9xNUBUwj/RnTZQ=
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
+381
@@ -0,0 +1,381 @@
|
|||||||
|
package tgbotapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUserStringWith(t *testing.T) {
|
||||||
|
user := User{
|
||||||
|
ID: 0,
|
||||||
|
FirstName: "Test",
|
||||||
|
LastName: "Test",
|
||||||
|
UserName: "",
|
||||||
|
LanguageCode: "en",
|
||||||
|
IsBot: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.String() != "Test Test" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserStringWithUserName(t *testing.T) {
|
||||||
|
user := User{
|
||||||
|
ID: 0,
|
||||||
|
FirstName: "Test",
|
||||||
|
LastName: "Test",
|
||||||
|
UserName: "@test",
|
||||||
|
LanguageCode: "en",
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.String() != "@test" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMessageTime(t *testing.T) {
|
||||||
|
message := Message{Date: 0}
|
||||||
|
|
||||||
|
date := time.Unix(0, 0)
|
||||||
|
if message.Time() != date {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMessageIsCommandWithCommand(t *testing.T) {
|
||||||
|
message := Message{Text: "/command"}
|
||||||
|
message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
|
||||||
|
|
||||||
|
if !message.IsCommand() {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsCommandWithText(t *testing.T) {
|
||||||
|
message := Message{Text: "some text"}
|
||||||
|
|
||||||
|
if message.IsCommand() {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsCommandWithEmptyText(t *testing.T) {
|
||||||
|
message := Message{Text: ""}
|
||||||
|
|
||||||
|
if message.IsCommand() {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandWithCommand(t *testing.T) {
|
||||||
|
message := Message{Text: "/command"}
|
||||||
|
message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
|
||||||
|
|
||||||
|
if message.Command() != "command" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandWithEmptyText(t *testing.T) {
|
||||||
|
message := Message{Text: ""}
|
||||||
|
|
||||||
|
if message.Command() != "" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandWithNonCommand(t *testing.T) {
|
||||||
|
message := Message{Text: "test text"}
|
||||||
|
|
||||||
|
if message.Command() != "" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandWithBotName(t *testing.T) {
|
||||||
|
message := Message{Text: "/command@testbot"}
|
||||||
|
message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 16}}
|
||||||
|
|
||||||
|
if message.Command() != "command" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandWithAtWithBotName(t *testing.T) {
|
||||||
|
message := Message{Text: "/command@testbot"}
|
||||||
|
message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 16}}
|
||||||
|
|
||||||
|
if message.CommandWithAt() != "command@testbot" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMessageCommandArgumentsWithArguments(t *testing.T) {
|
||||||
|
message := Message{Text: "/command with arguments"}
|
||||||
|
message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
|
||||||
|
if message.CommandArguments() != "with arguments" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMessageCommandArgumentsWithMalformedArguments(t *testing.T) {
|
||||||
|
message := Message{Text: "/command-without argument space"}
|
||||||
|
message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
|
||||||
|
if message.CommandArguments() != "without argument space" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMessageCommandArgumentsWithoutArguments(t *testing.T) {
|
||||||
|
message := Message{Text: "/command"}
|
||||||
|
if message.CommandArguments() != "" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMessageCommandArgumentsForNonCommand(t *testing.T) {
|
||||||
|
message := Message{Text: "test text"}
|
||||||
|
if message.CommandArguments() != "" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMessageEntityParseURLGood(t *testing.T) {
|
||||||
|
entity := MessageEntity{URL: "https://www.google.com"}
|
||||||
|
|
||||||
|
if _, err := entity.ParseURL(); err != nil {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMessageEntityParseURLBad(t *testing.T) {
|
||||||
|
entity := MessageEntity{URL: ""}
|
||||||
|
|
||||||
|
if _, err := entity.ParseURL(); err == nil {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChatIsPrivate(t *testing.T) {
|
||||||
|
chat := Chat{ID: 10, Type: "private"}
|
||||||
|
|
||||||
|
if !chat.IsPrivate() {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChatIsGroup(t *testing.T) {
|
||||||
|
chat := Chat{ID: 10, Type: "group"}
|
||||||
|
|
||||||
|
if !chat.IsGroup() {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChatIsChannel(t *testing.T) {
|
||||||
|
chat := Chat{ID: 10, Type: "channel"}
|
||||||
|
|
||||||
|
if !chat.IsChannel() {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChatIsSuperGroup(t *testing.T) {
|
||||||
|
chat := Chat{ID: 10, Type: "supergroup"}
|
||||||
|
|
||||||
|
if !chat.IsSuperGroup() {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMessageEntityIsMention(t *testing.T) {
|
||||||
|
entity := MessageEntity{Type: "mention"}
|
||||||
|
|
||||||
|
if !entity.IsMention() {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMessageEntityIsHashtag(t *testing.T) {
|
||||||
|
entity := MessageEntity{Type: "hashtag"}
|
||||||
|
|
||||||
|
if !entity.IsHashtag() {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMessageEntityIsBotCommand(t *testing.T) {
|
||||||
|
entity := MessageEntity{Type: "bot_command"}
|
||||||
|
|
||||||
|
if !entity.IsCommand() {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMessageEntityIsUrl(t *testing.T) {
|
||||||
|
entity := MessageEntity{Type: "url"}
|
||||||
|
|
||||||
|
if !entity.IsURL() {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMessageEntityIsEmail(t *testing.T) {
|
||||||
|
entity := MessageEntity{Type: "email"}
|
||||||
|
|
||||||
|
if !entity.IsEmail() {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMessageEntityIsBold(t *testing.T) {
|
||||||
|
entity := MessageEntity{Type: "bold"}
|
||||||
|
|
||||||
|
if !entity.IsBold() {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMessageEntityIsItalic(t *testing.T) {
|
||||||
|
entity := MessageEntity{Type: "italic"}
|
||||||
|
|
||||||
|
if !entity.IsItalic() {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMessageEntityIsCode(t *testing.T) {
|
||||||
|
entity := MessageEntity{Type: "code"}
|
||||||
|
|
||||||
|
if !entity.IsCode() {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMessageEntityIsPre(t *testing.T) {
|
||||||
|
entity := MessageEntity{Type: "pre"}
|
||||||
|
|
||||||
|
if !entity.IsPre() {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMessageEntityIsTextLink(t *testing.T) {
|
||||||
|
entity := MessageEntity{Type: "text_link"}
|
||||||
|
|
||||||
|
if !entity.IsTextLink() {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileLink(t *testing.T) {
|
||||||
|
file := File{FilePath: "test/test.txt"}
|
||||||
|
|
||||||
|
if file.Link("token") != "https://api.telegram.org/file/bottoken/test/test.txt" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure all configs are sendable
|
||||||
|
var (
|
||||||
|
_ Chattable = AnimationConfig{}
|
||||||
|
_ Chattable = AnswerWebAppQueryConfig{}
|
||||||
|
_ Chattable = AudioConfig{}
|
||||||
|
_ Chattable = BanChatMemberConfig{}
|
||||||
|
_ Chattable = BanChatSenderChatConfig{}
|
||||||
|
_ Chattable = CallbackConfig{}
|
||||||
|
_ Chattable = ChatActionConfig{}
|
||||||
|
_ Chattable = ChatAdministratorsConfig{}
|
||||||
|
_ Chattable = ChatInfoConfig{}
|
||||||
|
_ Chattable = ChatInviteLinkConfig{}
|
||||||
|
_ Chattable = CloseConfig{}
|
||||||
|
_ Chattable = ContactConfig{}
|
||||||
|
_ Chattable = CopyMessageConfig{}
|
||||||
|
_ Chattable = CreateChatInviteLinkConfig{}
|
||||||
|
_ Chattable = DeleteChatPhotoConfig{}
|
||||||
|
_ Chattable = DeleteChatStickerSetConfig{}
|
||||||
|
_ Chattable = DeleteMessageConfig{}
|
||||||
|
_ Chattable = DeleteMyCommandsConfig{}
|
||||||
|
_ Chattable = DeleteWebhookConfig{}
|
||||||
|
_ Chattable = DocumentConfig{}
|
||||||
|
_ Chattable = EditChatInviteLinkConfig{}
|
||||||
|
_ Chattable = EditMessageCaptionConfig{}
|
||||||
|
_ Chattable = EditMessageLiveLocationConfig{}
|
||||||
|
_ Chattable = EditMessageMediaConfig{}
|
||||||
|
_ Chattable = EditMessageReplyMarkupConfig{}
|
||||||
|
_ Chattable = EditMessageTextConfig{}
|
||||||
|
_ Chattable = FileConfig{}
|
||||||
|
_ Chattable = ForwardConfig{}
|
||||||
|
_ Chattable = GameConfig{}
|
||||||
|
_ Chattable = GetChatMemberConfig{}
|
||||||
|
_ Chattable = GetChatMenuButtonConfig{}
|
||||||
|
_ Chattable = GetGameHighScoresConfig{}
|
||||||
|
_ Chattable = GetMyDefaultAdministratorRightsConfig{}
|
||||||
|
_ Chattable = InlineConfig{}
|
||||||
|
_ Chattable = InvoiceConfig{}
|
||||||
|
_ Chattable = KickChatMemberConfig{}
|
||||||
|
_ Chattable = LeaveChatConfig{}
|
||||||
|
_ Chattable = LocationConfig{}
|
||||||
|
_ Chattable = LogOutConfig{}
|
||||||
|
_ Chattable = MediaGroupConfig{}
|
||||||
|
_ Chattable = MessageConfig{}
|
||||||
|
_ Chattable = PhotoConfig{}
|
||||||
|
_ Chattable = PinChatMessageConfig{}
|
||||||
|
_ Chattable = PreCheckoutConfig{}
|
||||||
|
_ Chattable = PromoteChatMemberConfig{}
|
||||||
|
_ Chattable = RestrictChatMemberConfig{}
|
||||||
|
_ Chattable = RevokeChatInviteLinkConfig{}
|
||||||
|
_ Chattable = SendPollConfig{}
|
||||||
|
_ Chattable = SetChatDescriptionConfig{}
|
||||||
|
_ Chattable = SetChatMenuButtonConfig{}
|
||||||
|
_ Chattable = SetChatPhotoConfig{}
|
||||||
|
_ Chattable = SetChatTitleConfig{}
|
||||||
|
_ Chattable = SetGameScoreConfig{}
|
||||||
|
_ Chattable = SetMyDefaultAdministratorRightsConfig{}
|
||||||
|
_ Chattable = ShippingConfig{}
|
||||||
|
_ Chattable = StickerConfig{}
|
||||||
|
_ Chattable = StopMessageLiveLocationConfig{}
|
||||||
|
_ Chattable = StopPollConfig{}
|
||||||
|
_ Chattable = UnbanChatMemberConfig{}
|
||||||
|
_ Chattable = UnbanChatSenderChatConfig{}
|
||||||
|
_ Chattable = UnpinChatMessageConfig{}
|
||||||
|
_ Chattable = UpdateConfig{}
|
||||||
|
_ Chattable = UserProfilePhotosConfig{}
|
||||||
|
_ Chattable = VenueConfig{}
|
||||||
|
_ Chattable = VideoConfig{}
|
||||||
|
_ Chattable = VideoNoteConfig{}
|
||||||
|
_ Chattable = VoiceConfig{}
|
||||||
|
_ Chattable = WebhookConfig{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ensure all Fileable types are correct.
|
||||||
|
var (
|
||||||
|
_ Fileable = (*PhotoConfig)(nil)
|
||||||
|
_ Fileable = (*AudioConfig)(nil)
|
||||||
|
_ Fileable = (*DocumentConfig)(nil)
|
||||||
|
_ Fileable = (*StickerConfig)(nil)
|
||||||
|
_ Fileable = (*VideoConfig)(nil)
|
||||||
|
_ Fileable = (*AnimationConfig)(nil)
|
||||||
|
_ Fileable = (*VideoNoteConfig)(nil)
|
||||||
|
_ Fileable = (*VoiceConfig)(nil)
|
||||||
|
_ Fileable = (*SetChatPhotoConfig)(nil)
|
||||||
|
_ Fileable = (*EditMessageMediaConfig)(nil)
|
||||||
|
_ Fileable = (*SetChatPhotoConfig)(nil)
|
||||||
|
_ Fileable = (*UploadStickerConfig)(nil)
|
||||||
|
_ Fileable = (*NewStickerSetConfig)(nil)
|
||||||
|
_ Fileable = (*AddStickerConfig)(nil)
|
||||||
|
_ Fileable = (*MediaGroupConfig)(nil)
|
||||||
|
_ Fileable = (*WebhookConfig)(nil)
|
||||||
|
_ Fileable = (*SetStickerSetThumbConfig)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ensure all RequestFileData types are correct.
|
||||||
|
var (
|
||||||
|
_ RequestFileData = (*FilePath)(nil)
|
||||||
|
_ RequestFileData = (*FileBytes)(nil)
|
||||||
|
_ RequestFileData = (*FileReader)(nil)
|
||||||
|
_ RequestFileData = (*FileURL)(nil)
|
||||||
|
_ RequestFileData = (*FileID)(nil)
|
||||||
|
_ RequestFileData = (*fileAttach)(nil)
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user