Merge branch 'develop' into bot-api-5.0

bot-api-6.1
Syfaro 2020-11-06 00:18:30 -05:00
commit 24e02f7ba6
8 changed files with 2597 additions and 857 deletions

View File

@ -3,7 +3,7 @@
[![GoDoc](https://godoc.org/github.com/go-telegram-bot-api/telegram-bot-api?status.svg)](http://godoc.org/github.com/go-telegram-bot-api/telegram-bot-api) [![GoDoc](https://godoc.org/github.com/go-telegram-bot-api/telegram-bot-api?status.svg)](http://godoc.org/github.com/go-telegram-bot-api/telegram-bot-api)
[![Travis](https://travis-ci.org/go-telegram-bot-api/telegram-bot-api.svg)](https://travis-ci.org/go-telegram-bot-api/telegram-bot-api) [![Travis](https://travis-ci.org/go-telegram-bot-api/telegram-bot-api.svg)](https://travis-ci.org/go-telegram-bot-api/telegram-bot-api)
All methods are fairly self explanatory, and reading the godoc page should All methods are fairly self explanatory, and reading the [godoc](http://godoc.org/github.com/go-telegram-bot-api/telegram-bot-api) page should
explain everything. If something isn't clear, open an issue or submit explain everything. If something isn't clear, open an issue or submit
a pull request. a pull request.

51
bot.go
View File

@ -18,6 +18,12 @@ import (
"github.com/technoweenie/multipartstreamer" "github.com/technoweenie/multipartstreamer"
) )
// HTTPClient is the type needed for the bot to perform HTTP requests.
type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
PostForm(url string, data url.Values) (*http.Response, error)
}
// BotAPI allows you to interact with the Telegram Bot API. // BotAPI allows you to interact with the Telegram Bot API.
type BotAPI struct { type BotAPI struct {
Token string `json:"token"` Token string `json:"token"`
@ -25,7 +31,7 @@ type BotAPI struct {
Buffer int `json:"buffer"` Buffer int `json:"buffer"`
Self User `json:"-"` Self User `json:"-"`
Client *http.Client `json:"-"` Client HTTPClient `json:"-"`
shutdownChannel chan interface{} shutdownChannel chan interface{}
apiEndpoint string apiEndpoint string
@ -35,21 +41,29 @@ type BotAPI struct {
// //
// It requires a token, provided by @BotFather on Telegram. // It requires a token, provided by @BotFather on Telegram.
func NewBotAPI(token string) (*BotAPI, error) { func NewBotAPI(token string) (*BotAPI, error) {
return NewBotAPIWithClient(token, &http.Client{}) 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 // NewBotAPIWithClient creates a new BotAPI instance
// and allows you to pass a http.Client. // and allows you to pass a http.Client.
// //
// It requires a token, provided by @BotFather on Telegram. // It requires a token, provided by @BotFather on Telegram and API endpoint.
func NewBotAPIWithClient(token string, client *http.Client) (*BotAPI, error) { func NewBotAPIWithClient(token, apiEndpoint string, client HTTPClient) (*BotAPI, error) {
bot := &BotAPI{ bot := &BotAPI{
Token: token, Token: token,
Client: client, Client: client,
Buffer: 100, Buffer: 100,
shutdownChannel: make(chan interface{}), shutdownChannel: make(chan interface{}),
apiEndpoint: APIEndpoint, apiEndpoint: apiEndpoint,
} }
self, err := bot.GetMe() self, err := bot.GetMe()
@ -413,6 +427,7 @@ func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) UpdatesChannel {
for { for {
select { select {
case <-bot.shutdownChannel: case <-bot.shutdownChannel:
close(ch)
return return
default: default:
} }
@ -451,21 +466,35 @@ func (bot *BotAPI) ListenForWebhook(pattern string) UpdatesChannel {
ch := make(chan Update, bot.Buffer) ch := make(chan Update, bot.Buffer)
http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) { http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
ch <- bot.HandleUpdate(w, r) 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 return ch
} }
// HandleUpdate parses and returns update received via webhook // HandleUpdate parses and returns update received via webhook
func (bot *BotAPI) HandleUpdate(res http.ResponseWriter, req *http.Request) Update { func (bot *BotAPI) HandleUpdate(r *http.Request) (*Update, error) {
bytes, _ := ioutil.ReadAll(req.Body) if r.Method != http.MethodPost {
req.Body.Close() err := errors.New("wrong HTTP method required POST")
return nil, err
}
var update Update var update Update
json.Unmarshal(bytes, &update) err := json.NewDecoder(r.Body).Decode(&update)
if err != nil {
return nil, err
}
return update return &update, nil
} }
// WriteToHTTPResponse writes the request to the HTTP ResponseWriter. // WriteToHTTPResponse writes the request to the HTTP ResponseWriter.

View File

@ -23,10 +23,25 @@ const (
ExistingStickerFileID = "BQADAgADcwADjMcoCbdl-6eB--YPAg" ExistingStickerFileID = "BQADAgADcwADjMcoCbdl-6eB--YPAg"
) )
type testLogger struct {
t *testing.T
}
func (t testLogger) Println(v ...interface{}) {
t.t.Log(v...)
}
func (t testLogger) Printf(format string, v ...interface{}) {
t.t.Logf(format, v...)
}
func getBot(t *testing.T) (*BotAPI, error) { func getBot(t *testing.T) (*BotAPI, error) {
bot, err := NewBotAPI(TestToken) bot, err := NewBotAPI(TestToken)
bot.Debug = true bot.Debug = true
logger := testLogger{t}
SetLogger(logger)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -417,6 +432,32 @@ func TestSendWithExistingStickerAndKeyboardHide(t *testing.T) {
} }
} }
func TestSendWithDice(t *testing.T) {
bot, _ := getBot(t)
msg := NewDice(ChatID)
_, err := bot.Send(msg)
if err != nil {
t.Error(err)
t.Fail()
}
}
func TestSendWithDiceWithEmoji(t *testing.T) {
bot, _ := getBot(t)
msg := NewDiceWithEmoji(ChatID, "🏀")
_, err := bot.Send(msg)
if err != nil {
t.Error(err)
t.Fail()
}
}
func TestGetFile(t *testing.T) { func TestGetFile(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
@ -634,7 +675,12 @@ func ExampleWebhookHandler() {
} }
http.HandleFunc("/"+bot.Token, func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/"+bot.Token, func(w http.ResponseWriter, r *http.Request) {
log.Printf("%+v\n", bot.HandleUpdate(w, r)) update, err := bot.HandleUpdate(r)
if err != nil {
log.Printf("%+v\n", err.Error())
} else {
log.Printf("%+v\n", *update)
}
}) })
go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil) go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil)

View File

@ -1001,14 +1001,11 @@ func (config InlineConfig) params() (Params, error) {
params.AddNonEmpty("next_offset", config.NextOffset) params.AddNonEmpty("next_offset", config.NextOffset)
params.AddNonEmpty("switch_pm_text", config.SwitchPMText) params.AddNonEmpty("switch_pm_text", config.SwitchPMText)
params.AddNonEmpty("switch_pm_parameter", config.SwitchPMParameter) params.AddNonEmpty("switch_pm_parameter", config.SwitchPMParameter)
err := params.AddInterface("results", config.Results)
if err := params.AddInterface("results", config.Results); err != nil {
return params, err return params, err
} }
return params, nil
}
// CallbackConfig contains information on making a CallbackQuery response. // CallbackConfig contains information on making a CallbackQuery response.
type CallbackConfig struct { type CallbackConfig struct {
CallbackQueryID string `json:"callback_query_id"` CallbackQueryID string `json:"callback_query_id"`
@ -1100,12 +1097,10 @@ func (config RestrictChatMemberConfig) params() (Params, error) {
params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername, config.ChannelUsername) params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername, config.ChannelUsername)
params.AddNonZero("user_id", config.UserID) params.AddNonZero("user_id", config.UserID)
if err := params.AddInterface("permissions", config.Permissions); err != nil { err := params.AddInterface("permissions", config.Permissions)
return params, err
}
params.AddNonZero64("until_date", config.UntilDate) params.AddNonZero64("until_date", config.UntilDate)
return params, nil return params, err
} }
// PromoteChatMemberConfig contains fields to promote members of chat // PromoteChatMemberConfig contains fields to promote members of chat
@ -1327,10 +1322,7 @@ func (config InvoiceConfig) params() (Params, error) {
params["start_parameter"] = config.StartParameter params["start_parameter"] = config.StartParameter
params["currency"] = config.Currency params["currency"] = config.Currency
if err = params.AddInterface("prices", config.Prices); err != nil { err = params.AddInterface("prices", config.Prices)
return params, err
}
params.AddNonEmpty("provider_data", config.ProviderData) params.AddNonEmpty("provider_data", config.ProviderData)
params.AddNonEmpty("photo_url", config.PhotoURL) params.AddNonEmpty("photo_url", config.PhotoURL)
params.AddNonZero("photo_size", config.PhotoSize) params.AddNonZero("photo_size", config.PhotoSize)
@ -1344,7 +1336,7 @@ func (config InvoiceConfig) params() (Params, error) {
params.AddBool("send_phone_number_to_provider", config.SendPhoneNumberToProvider) params.AddBool("send_phone_number_to_provider", config.SendPhoneNumberToProvider)
params.AddBool("send_email_to_provider", config.SendEmailToProvider) params.AddBool("send_email_to_provider", config.SendEmailToProvider)
return params, nil return params, err
} }
func (config InvoiceConfig) method() string { func (config InvoiceConfig) method() string {
@ -1359,6 +1351,21 @@ type ShippingConfig struct {
ErrorMessage string ErrorMessage string
} }
func (config ShippingConfig) method() string {
return "answerShippingQuery"
}
func (config ShippingConfig) params() (Params, error) {
params := make(Params)
params["shipping_query_id"] = config.ShippingQueryID
params.AddBool("ok", config.OK)
err := params.AddInterface("shipping_options", config.ShippingOptions)
params.AddNonEmpty("error_message", config.ErrorMessage)
return params, err
}
// PreCheckoutConfig conatins information for answerPreCheckoutQuery request. // PreCheckoutConfig conatins information for answerPreCheckoutQuery request.
type PreCheckoutConfig struct { type PreCheckoutConfig struct {
PreCheckoutQueryID string // required PreCheckoutQueryID string // required
@ -1366,6 +1373,20 @@ type PreCheckoutConfig struct {
ErrorMessage string ErrorMessage string
} }
func (config PreCheckoutConfig) method() string {
return "answerPreCheckoutQuery"
}
func (config PreCheckoutConfig) params() (Params, error) {
params := make(Params)
params["pre_checkout_query_id"] = config.PreCheckoutQueryID
params.AddBool("ok", config.OK)
params.AddNonEmpty("error_message", config.ErrorMessage)
return params, nil
}
// DeleteMessageConfig contains information of a message in a chat to delete. // DeleteMessageConfig contains information of a message in a chat to delete.
type DeleteMessageConfig struct { type DeleteMessageConfig struct {
ChannelUsername string ChannelUsername string
@ -1828,31 +1849,6 @@ func (config MediaGroupConfig) params() (Params, error) {
return params, nil return params, nil
} }
// DiceConfig allows you to send a random dice roll to Telegram.
//
// Emoji may be one of the following: 🎲 (1-6), 🎯 (1-6), 🏀 (1-5), ⚽ (1-5),
// 🎰 (1-64).
type DiceConfig struct {
BaseChat
Emoji string
}
func (config DiceConfig) method() string {
return "sendDice"
}
func (config DiceConfig) params() (Params, error) {
params, err := config.BaseChat.params()
if err != nil {
return params, err
}
params.AddNonEmpty("emoji", config.Emoji)
return params, err
}
// GetMyCommandsConfig gets a list of the currently registered commands. // GetMyCommandsConfig gets a list of the currently registered commands.
type GetMyCommandsConfig struct{} type GetMyCommandsConfig struct{}
@ -1861,7 +1857,7 @@ func (config GetMyCommandsConfig) method() string {
} }
func (config GetMyCommandsConfig) params() (Params, error) { func (config GetMyCommandsConfig) params() (Params, error) {
return make(Params), nil return nil, nil
} }
// SetMyCommandsConfig sets a list of commands the bot understands. // SetMyCommandsConfig sets a list of commands the bot understands.
@ -1880,3 +1876,28 @@ func (config SetMyCommandsConfig) params() (Params, error) {
return params, err return params, err
} }
// DiceConfig contains information about a sendDice request.
type DiceConfig struct {
BaseChat
// Emoji on which the dice throw animation is based.
// Currently, must be one of “🎲”, “🎯”, or “🏀”.
// Dice can have values 1-6 for “🎲” and “🎯”, and values 1-5 for “🏀”.
// Defaults to “🎲”
Emoji string
}
func (config DiceConfig) method() string {
return "sendDice"
}
func (config DiceConfig) params() (Params, error) {
params, err := config.BaseChat.params()
if err != nil {
return params, err
}
params.AddNonEmpty("emoji", config.Emoji)
return params, err
}

View File

@ -29,7 +29,8 @@ func NewDeleteMessage(chatID int64, messageID int) DeleteMessageConfig {
// NewMessageToChannel creates a new Message that is sent to a channel // NewMessageToChannel creates a new Message that is sent to a channel
// by username. // by username.
// //
// username is the username of the channel, text is the message text. // 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 { func NewMessageToChannel(username string, text string) MessageConfig {
return MessageConfig{ return MessageConfig{
BaseChat: BaseChat{ BaseChat: BaseChat{
@ -527,7 +528,7 @@ func NewInlineQueryResultCachedGIF(id, gifID string) InlineQueryResultCachedGIF
return InlineQueryResultCachedGIF{ return InlineQueryResultCachedGIF{
Type: "gif", Type: "gif",
ID: id, ID: id,
GifID: gifID, GIFID: gifID,
} }
} }
@ -540,12 +541,12 @@ func NewInlineQueryResultMPEG4GIF(id, url string) InlineQueryResultMPEG4GIF {
} }
} }
// NewInlineQueryResultCachedMPEG4GIF create a new inline query with cached photo. // NewInlineQueryResultCachedMPEG4GIF create a new inline query with cached MPEG4 GIF.
func NewInlineQueryResultCachedMPEG4GIF(id, MPEG4GifID string) InlineQueryResultCachedMpeg4Gif { func NewInlineQueryResultCachedMPEG4GIF(id, MPEG4GIFID string) InlineQueryResultCachedMPEG4GIF {
return InlineQueryResultCachedMpeg4Gif{ return InlineQueryResultCachedMPEG4GIF{
Type: "mpeg4_gif", Type: "mpeg4_gif",
ID: id, ID: id,
MGifID: MPEG4GifID, MPEG4FileID: MPEG4GIFID,
} }
} }
@ -700,6 +701,18 @@ func NewEditMessageText(chatID int64, messageID int, text string) EditMessageTex
} }
} }
// 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. // NewEditMessageCaption allows you to edit the caption of a message.
func NewEditMessageCaption(chatID int64, messageID int, caption string) EditMessageCaptionConfig { func NewEditMessageCaption(chatID int64, messageID int, caption string) EditMessageCaptionConfig {
return EditMessageCaptionConfig{ return EditMessageCaptionConfig{
@ -723,17 +736,6 @@ func NewEditMessageReplyMarkup(chatID int64, messageID int, replyMarkup InlineKe
} }
} }
// NewHideKeyboard hides the keyboard, with the option for being selective
// or hiding for everyone.
func NewHideKeyboard(selective bool) ReplyKeyboardHide {
log.Println("NewHideKeyboard is deprecated, please use NewRemoveKeyboard")
return ReplyKeyboardHide{
HideKeyboard: true,
Selective: selective,
}
}
// NewRemoveKeyboard hides the keyboard, with the option for being selective // NewRemoveKeyboard hides the keyboard, with the option for being selective
// or hiding for everyone. // or hiding for everyone.
func NewRemoveKeyboard(selective bool) ReplyKeyboardRemove { func NewRemoveKeyboard(selective bool) ReplyKeyboardRemove {

View File

@ -174,3 +174,21 @@ func TestNewEditMessageReplyMarkup(t *testing.T) {
} }
} }
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()
}
}

2850
types.go

File diff suppressed because it is too large Load Diff

View File

@ -310,6 +310,7 @@ var (
_ Chattable = MessageConfig{} _ Chattable = MessageConfig{}
_ Chattable = PhotoConfig{} _ Chattable = PhotoConfig{}
_ Chattable = PinChatMessageConfig{} _ Chattable = PinChatMessageConfig{}
_ Chattable = PreCheckoutConfig{}
_ Chattable = PromoteChatMemberConfig{} _ Chattable = PromoteChatMemberConfig{}
_ Chattable = DeleteWebhookConfig{} _ Chattable = DeleteWebhookConfig{}
_ Chattable = RestrictChatMemberConfig{} _ Chattable = RestrictChatMemberConfig{}
@ -318,6 +319,7 @@ var (
_ Chattable = SetChatPhotoConfig{} _ Chattable = SetChatPhotoConfig{}
_ Chattable = SetChatTitleConfig{} _ Chattable = SetChatTitleConfig{}
_ Chattable = SetGameScoreConfig{} _ Chattable = SetGameScoreConfig{}
_ Chattable = ShippingConfig{}
_ Chattable = StickerConfig{} _ Chattable = StickerConfig{}
_ Chattable = StopPollConfig{} _ Chattable = StopPollConfig{}
_ Chattable = StopMessageLiveLocationConfig{} _ Chattable = StopMessageLiveLocationConfig{}