Merge pull request #1 from go-telegram-bot-api/master

sync
pull/164/head
Irek Fasikhov 7 years ago committed by GitHub
commit 2fcce18624
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      README.md
  2. 170
      bot.go
  3. 83
      bot_test.go
  4. 112
      configs.go
  5. 33
      helpers.go
  6. 63
      types.go
  7. 38
      types_test.go

@ -91,7 +91,13 @@ func main() {
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)

170
bot.go

@ -7,6 +7,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
@ -67,31 +68,49 @@ func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse,
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusForbidden {
return APIResponse{}, errors.New(ErrAPIForbidden)
var apiResp APIResponse
bytes, err := bot.decodeAPIResponse(resp.Body, &apiResp)
if err != nil {
return apiResp, err
}
if resp.StatusCode != http.StatusOK {
return APIResponse{}, errors.New(http.StatusText(resp.StatusCode))
if bot.Debug {
log.Printf("%s resp: %s", endpoint, bytes)
}
bytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return APIResponse{}, err
if !apiResp.Ok {
parameters := ResponseParameters{}
if apiResp.Parameters != nil {
parameters = *apiResp.Parameters
}
return apiResp, Error{apiResp.Description, parameters}
}
if bot.Debug {
log.Println(endpoint, string(bytes))
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, err error) {
if !bot.Debug {
dec := json.NewDecoder(responseBody)
err = dec.Decode(resp)
return
}
var apiResp APIResponse
json.Unmarshal(bytes, &apiResp)
// if debug, read reponse body
data, err := ioutil.ReadAll(responseBody)
if err != nil {
return
}
if !apiResp.Ok {
return apiResp, errors.New(apiResp.Description)
err = json.Unmarshal(data, resp)
if err != nil {
return
}
return apiResp, nil
return data, nil
}
// makeMessageRequest makes a request to a method that returns a Message.
@ -712,24 +731,28 @@ func (bot *BotAPI) RestrictChatMember(config RestrictChatMemberConfig) (APIRespo
}
v.Add("user_id", strconv.Itoa(config.UserID))
if &config.CanSendMessages != nil {
if config.CanSendMessages != nil {
v.Add("can_send_messages", strconv.FormatBool(*config.CanSendMessages))
}
if &config.CanSendMediaMessages != nil {
if config.CanSendMediaMessages != nil {
v.Add("can_send_media_messages", strconv.FormatBool(*config.CanSendMediaMessages))
}
if &config.CanSendOtherMessages != nil {
if config.CanSendOtherMessages != nil {
v.Add("can_send_other_messages", strconv.FormatBool(*config.CanSendOtherMessages))
}
if &config.CanAddWebPagePreviews != nil {
if config.CanAddWebPagePreviews != nil {
v.Add("can_add_web_page_previews", strconv.FormatBool(*config.CanAddWebPagePreviews))
}
if config.UntilDate != 0 {
v.Add("until_date", strconv.FormatInt(config.UntilDate, 10))
}
bot.debugLog("restrictChatMember", v, nil)
return bot.MakeRequest("restrictChatMember", v)
}
// PromoteChatMember add admin rights to user
func (bot *BotAPI) PromoteChatMember(config PromoteChatMemberConfig) (APIResponse, error) {
v := url.Values{}
@ -742,28 +765,28 @@ func (bot *BotAPI) PromoteChatMember(config PromoteChatMemberConfig) (APIRespons
}
v.Add("user_id", strconv.Itoa(config.UserID))
if &config.CanChangeInfo != nil {
if config.CanChangeInfo != nil {
v.Add("can_change_info", strconv.FormatBool(*config.CanChangeInfo))
}
if &config.CanPostMessages != nil {
if config.CanPostMessages != nil {
v.Add("can_post_messages", strconv.FormatBool(*config.CanPostMessages))
}
if &config.CanEditMessages != nil {
if config.CanEditMessages != nil {
v.Add("can_edit_messages", strconv.FormatBool(*config.CanEditMessages))
}
if &config.CanDeleteMessages != nil {
if config.CanDeleteMessages != nil {
v.Add("can_delete_messages", strconv.FormatBool(*config.CanDeleteMessages))
}
if &config.CanInviteUsers != nil {
if config.CanInviteUsers != nil {
v.Add("can_invite_users", strconv.FormatBool(*config.CanInviteUsers))
}
if &config.CanRestrictMembers != nil {
if config.CanRestrictMembers != nil {
v.Add("can_restrict_members", strconv.FormatBool(*config.CanRestrictMembers))
}
if &config.CanPinMessages != nil {
if config.CanPinMessages != nil {
v.Add("can_pin_messages", strconv.FormatBool(*config.CanPinMessages))
}
if &config.CanPromoteMembers != nil {
if config.CanPromoteMembers != nil {
v.Add("can_promote_members", strconv.FormatBool(*config.CanPromoteMembers))
}
@ -837,18 +860,93 @@ func (bot *BotAPI) DeleteMessage(config DeleteMessageConfig) (APIResponse, error
// GetInviteLink get InviteLink for a chat
func (bot *BotAPI) GetInviteLink(config ChatConfig) (string, error) {
v := url.Values{}
v := url.Values{}
if config.SuperGroupUsername == "" {
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
} else {
v.Add("chat_id", config.SuperGroupUsername)
}
resp, err := bot.MakeRequest("exportChatInviteLink", v)
if err != nil {
return "", err
}
var inviteLink string
err = json.Unmarshal(resp.Result, &inviteLink)
return inviteLink, err
}
// PinChatMessage pin message in supergroup
func (bot *BotAPI) PinChatMessage(config PinChatMessageConfig) (APIResponse, error) {
v, err := config.values()
if err != nil {
return APIResponse{}, err
}
bot.debugLog(config.method(), v, nil)
return bot.MakeRequest(config.method(), v)
}
if config.SuperGroupUsername == "" {
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
} else {
v.Add("chat_id", config.SuperGroupUsername)
}
// UnpinChatMessage unpin message in supergroup
func (bot *BotAPI) UnpinChatMessage(config UnpinChatMessageConfig) (APIResponse, error) {
v, err := config.values()
if err != nil {
return APIResponse{}, err
}
bot.debugLog(config.method(), v, nil)
return bot.MakeRequest(config.method(), v)
}
// SetChatTitle change title of chat.
func (bot *BotAPI) SetChatTitle(config SetChatTitleConfig) (APIResponse, error) {
v, err := config.values()
if err != nil {
return APIResponse{}, err
}
bot.debugLog(config.method(), v, nil)
return bot.MakeRequest(config.method(), v)
}
// SetChatDescription change description of chat.
func (bot *BotAPI) SetChatDescription(config SetChatDescriptionConfig) (APIResponse, error) {
v, err := config.values()
if err != nil {
return APIResponse{}, err
}
bot.debugLog(config.method(), v, nil)
return bot.MakeRequest(config.method(), v)
}
// SetChatPhoto change photo of chat.
func (bot *BotAPI) SetChatPhoto(config SetChatPhotoConfig) (APIResponse, error) {
params, err := config.params()
if err != nil {
return APIResponse{}, err
}
resp, err := bot.MakeRequest("exportChatInviteLink", v)
file := config.getFile()
var inviteLink string
err = json.Unmarshal(resp.Result, &inviteLink)
return bot.UploadFile(config.method(), params, config.name(), file)
}
return inviteLink, err
// DeleteChatPhoto delete photo of chat.
func (bot *BotAPI) DeleteChatPhoto(config DeleteChatPhotoConfig) (APIResponse, error) {
v, err := config.values()
if err != nil {
return APIResponse{}, err
}
bot.debugLog(config.method(), v, nil)
return bot.MakeRequest(config.method(), v)
}

@ -14,6 +14,7 @@ import (
const (
TestToken = "153667468:AAHlSHlMqSt1f_uFmVRJbm5gntu2HI4WW8I"
ChatID = 76918703
SupergroupChatID = -1001120141283
ReplyToMessageID = 35
ExistingPhotoFileID = "AgADAgADw6cxG4zHKAkr42N7RwEN3IFShCoABHQwXEtVks4EH2wBAAEC"
ExistingDocumentFileID = "BQADAgADOQADjMcoCcioX1GrDvp3Ag"
@ -372,7 +373,10 @@ func TestSendWithNewStickerAndKeyboardHide(t *testing.T) {
bot, _ := getBot(t)
msg := tgbotapi.NewStickerUpload(ChatID, "tests/image.jpg")
msg.ReplyMarkup = tgbotapi.ReplyKeyboardRemove{true, false}
msg.ReplyMarkup = tgbotapi.ReplyKeyboardRemove{
RemoveKeyboard: true,
Selective: false,
}
_, err := bot.Send(msg)
if err != nil {
@ -385,7 +389,10 @@ func TestSendWithExistingStickerAndKeyboardHide(t *testing.T) {
bot, _ := getBot(t)
msg := tgbotapi.NewStickerShare(ChatID, ExistingStickerFileID)
msg.ReplyMarkup = tgbotapi.ReplyKeyboardRemove{true, false}
msg.ReplyMarkup = tgbotapi.ReplyKeyboardRemove{
RemoveKeyboard: true,
Selective: false,
}
_, err := bot.Send(msg)
@ -398,7 +405,7 @@ func TestSendWithExistingStickerAndKeyboardHide(t *testing.T) {
func TestGetFile(t *testing.T) {
bot, _ := getBot(t)
file := tgbotapi.FileConfig{ExistingPhotoFileID}
file := tgbotapi.FileConfig{FileID: ExistingPhotoFileID}
_, err := bot.GetFile(file)
@ -466,7 +473,13 @@ func TestSetWebhookWithCert(t *testing.T) {
t.Error(err)
t.Fail()
}
info, err := bot.GetWebhookInfo()
if err != nil {
t.Error(err)
}
if info.LastErrorDate != 0 {
t.Errorf("[Telegram callback failed]%s", info.LastErrorMessage)
}
bot.RemoveWebhook()
}
@ -483,7 +496,13 @@ func TestSetWebhookWithoutCert(t *testing.T) {
t.Error(err)
t.Fail()
}
info, err := bot.GetWebhookInfo()
if err != nil {
t.Error(err)
}
if info.LastErrorDate != 0 {
t.Errorf("[Telegram callback failed]%s", info.LastErrorMessage)
}
bot.RemoveWebhook()
}
@ -548,7 +567,13 @@ func ExampleNewWebhook() {
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)
@ -609,3 +634,49 @@ func TestDeleteMessage(t *testing.T) {
t.Fail()
}
}
func TestPinChatMessage(t *testing.T) {
bot, _ := getBot(t)
msg := tgbotapi.NewMessage(SupergroupChatID, "A test message from the test library in telegram-bot-api")
msg.ParseMode = "markdown"
message, _ := bot.Send(msg)
pinChatMessageConfig := tgbotapi.PinChatMessageConfig{
ChatID: message.Chat.ID,
MessageID: message.MessageID,
DisableNotification: false,
}
_, err := bot.PinChatMessage(pinChatMessageConfig)
if err != nil {
t.Error(err)
t.Fail()
}
}
func TestUnpinChatMessage(t *testing.T) {
bot, _ := getBot(t)
msg := tgbotapi.NewMessage(SupergroupChatID, "A test message from the test library in telegram-bot-api")
msg.ParseMode = "markdown"
message, _ := bot.Send(msg)
// We need pin message to unpin something
pinChatMessageConfig := tgbotapi.PinChatMessageConfig{
ChatID: message.Chat.ID,
MessageID: message.MessageID,
DisableNotification: false,
}
_, err := bot.PinChatMessage(pinChatMessageConfig)
unpinChatMessageConfig := tgbotapi.UnpinChatMessageConfig{
ChatID: message.Chat.ID,
}
_, err = bot.UnpinChatMessage(unpinChatMessageConfig)
if err != nil {
t.Error(err)
t.Fail()
}
}

@ -676,7 +676,7 @@ type SetGameScoreConfig struct {
Score int
Force bool
DisableEditMessage bool
ChatID int
ChatID int64
ChannelUsername string
MessageID int
InlineMessageID string
@ -689,7 +689,7 @@ func (config SetGameScoreConfig) values() (url.Values, error) {
v.Add("score", strconv.Itoa(config.Score))
if config.InlineMessageID == "" {
if config.ChannelUsername == "" {
v.Add("chat_id", strconv.Itoa(config.ChatID))
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
} else {
v.Add("chat_id", config.ChannelUsername)
}
@ -1035,3 +1035,111 @@ func (config DeleteMessageConfig) values() (url.Values, error) {
return v, nil
}
// PinChatMessageConfig contains information of a message in a chat to pin.
type PinChatMessageConfig struct {
ChatID int64
MessageID int
DisableNotification bool
}
func (config PinChatMessageConfig) method() string {
return "pinChatMessage"
}
func (config PinChatMessageConfig) values() (url.Values, error) {
v := url.Values{}
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
v.Add("message_id", strconv.Itoa(config.MessageID))
v.Add("disable_notification", strconv.FormatBool(config.DisableNotification))
return v, nil
}
// UnpinChatMessageConfig contains information of chat to unpin.
type UnpinChatMessageConfig struct {
ChatID int64
}
func (config UnpinChatMessageConfig) method() string {
return "unpinChatMessage"
}
func (config UnpinChatMessageConfig) values() (url.Values, error) {
v := url.Values{}
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
return v, nil
}
// SetChatTitleConfig contains information for change chat title.
type SetChatTitleConfig struct {
ChatID int64
Title string
}
func (config SetChatTitleConfig) method() string {
return "setChatTitle"
}
func (config SetChatTitleConfig) values() (url.Values, error) {
v := url.Values{}
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
v.Add("title", config.Title)
return v, nil
}
// SetChatDescriptionConfig contains information for change chat description.
type SetChatDescriptionConfig struct {
ChatID int64
Description string
}
func (config SetChatDescriptionConfig) method() string {
return "setChatDescription"
}
func (config SetChatDescriptionConfig) values() (url.Values, error) {
v := url.Values{}
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
v.Add("description", config.Description)
return v, nil
}
// SetChatPhotoConfig contains information for change chat photo
type SetChatPhotoConfig struct {
BaseFile
}
// name returns the field name for the Photo.
func (config SetChatPhotoConfig) name() string {
return "photo"
}
// method returns Telegram API method name for sending Photo.
func (config SetChatPhotoConfig) method() string {
return "setChatPhoto"
}
// DeleteChatPhotoConfig contains information for delete chat photo.
type DeleteChatPhotoConfig struct {
ChatID int64
}
func (config DeleteChatPhotoConfig) method() string {
return "deleteChatPhoto"
}
func (config DeleteChatPhotoConfig) values() (url.Values, error) {
v := url.Values{}
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
return v, nil
}

@ -641,7 +641,7 @@ func NewCallbackWithAlert(id, text string) CallbackConfig {
}
}
// NewInvoice created a new Invoice request to the user.
// 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},
@ -653,3 +653,34 @@ func NewInvoice(chatID int64, title, description, payload, providerToken, startP
Currency: currency,
Prices: prices}
}
// NewSetChatPhotoUpload creates a new chat photo uploader.
//
// chatID is where to send it, file is a string path to the file,
// FileReader, or FileBytes.
//
// Note that you must send animated GIFs as a document.
func NewSetChatPhotoUpload(chatID int64, file interface{}) SetChatPhotoConfig {
return SetChatPhotoConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
File: file,
UseExisting: false,
},
}
}
// NewSetChatPhotoShare shares an existing photo.
// You may use this to reshare an existing photo without reuploading it.
//
// chatID is where to send it, fileID is the ID of the file
// already uploaded.
func NewSetChatPhotoShare(chatID int64, fileID string) SetChatPhotoConfig {
return SetChatPhotoConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
FileID: fileID,
UseExisting: true,
},
}
}

@ -56,6 +56,7 @@ type User struct {
LastName string `json:"last_name"` // optional
UserName string `json:"username"` // optional
LanguageCode string `json:"language_code"` // optional
IsBot bool `json:"is_bot"` // optional
}
// String displays a simple text version of a user.
@ -173,21 +174,23 @@ func (m *Message) Time() time.Time {
return time.Unix(int64(m.Date), 0)
}
// IsCommand returns true if message starts with '/'.
// IsCommand returns true if message starts with a "bot_command" entity.
func (m *Message) IsCommand() bool {
return m.Text != "" && m.Text[0] == '/'
if m.Entities == nil || len(*m.Entities) == 0 {
return false
}
entity := (*m.Entities)[0]
return entity.Offset == 0 && entity.Type == "bot_command"
}
// Command checks if the message was a command and if it was, returns the
// command. If the Message was not a command, it returns an empty string.
//
// If the command contains the at bot syntax, it removes the bot name.
// If the command contains the at name syntax, it is removed. Use
// CommandWithAt() if you do not want that.
func (m *Message) Command() string {
if !m.IsCommand() {
return ""
}
command := strings.SplitN(m.Text, " ", 2)[0][1:]
command := m.CommandWithAt()
if i := strings.Index(command, "@"); i != -1 {
command = command[:i]
@ -196,20 +199,42 @@ func (m *Message) Command() string {
return command
}
// CommandWithAt checks if the message was a command and if it was, returns the
// command. If the Message was not a command, it returns an empty string.
//
// If the command contains the at name syntax, it is not removed. Use Command()
// if you want that.
func (m *Message) CommandWithAt() string {
if !m.IsCommand() {
return ""
}
// IsCommand() checks that the message begins with a bot_command entity
entity := (*m.Entities)[0]
return m.Text[1:entity.Length]
}
// CommandArguments checks if the message was a command and if it was,
// returns all text after the command name. If the Message was not a
// command, it returns an empty string.
//
// Note: The first character after the command name is omitted:
// - "/foo bar baz" yields "bar baz", not " bar baz"
// - "/foo-bar baz" yields "bar baz", too
// Even though the latter is not a command conforming to the spec, the API
// marks "/foo" as command entity.
func (m *Message) CommandArguments() string {
if !m.IsCommand() {
return ""
}
split := strings.SplitN(m.Text, " ", 2)
if len(split) != 2 {
return ""
// IsCommand() checks that the message begins with a bot_command entity
entity := (*m.Entities)[0]
if len(m.Text) == entity.Length {
return "" // The command makes up the whole message
}
return split[1]
return m.Text[entity.Length+1:]
}
// MessageEntity contains information about data in a Message.
@ -385,7 +410,7 @@ type InlineKeyboardButton struct {
SwitchInlineQuery *string `json:"switch_inline_query,omitempty"` // optional
SwitchInlineQueryCurrentChat *string `json:"switch_inline_query_current_chat,omitempty"` // optional
CallbackGame *CallbackGame `json:"callback_game,omitempty"` // optional
Pay bool `json:"pay,omitempty"` // optional
Pay bool `json:"pay,omitempty"` // optional
}
// CallbackQuery is data sent when a keyboard button with callback data
@ -632,7 +657,7 @@ type InlineQueryResultGame struct {
Type string `json:"type"`
ID string `json:"id"`
GameShortName string `json:"game_short_name"`
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup"`
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
// ChosenInlineResult is an inline query result chosen by a User
@ -746,3 +771,13 @@ type PreCheckoutQuery struct {
ShippingOptionID string `json:"shipping_option_id,omitempty"`
OrderInfo *OrderInfo `json:"order_info,omitempty"`
}
// Error is an error containing extra information returned by the Telegram API.
type Error struct {
Message string
ResponseParameters
}
func (e Error) Error() string {
return e.Message
}

@ -8,7 +8,14 @@ import (
)
func TestUserStringWith(t *testing.T) {
user := tgbotapi.User{0, "Test", "Test", "", "en"}
user := tgbotapi.User{
ID: 0,
FirstName: "Test",
LastName: "Test",
UserName: "",
LanguageCode: "en",
IsBot: false,
}
if user.String() != "Test Test" {
t.Fail()
@ -16,7 +23,13 @@ func TestUserStringWith(t *testing.T) {
}
func TestUserStringWithUserName(t *testing.T) {
user := tgbotapi.User{0, "Test", "Test", "@test", "en"}
user := tgbotapi.User{
ID: 0,
FirstName: "Test",
LastName: "Test",
UserName: "@test",
LanguageCode: "en",
}
if user.String() != "@test" {
t.Fail()
@ -34,6 +47,7 @@ func TestMessageTime(t *testing.T) {
func TestMessageIsCommandWithCommand(t *testing.T) {
message := tgbotapi.Message{Text: "/command"}
message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
if message.IsCommand() != true {
t.Fail()
@ -58,6 +72,7 @@ func TestIsCommandWithEmptyText(t *testing.T) {
func TestCommandWithCommand(t *testing.T) {
message := tgbotapi.Message{Text: "/command"}
message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
if message.Command() != "command" {
t.Fail()
@ -82,19 +97,38 @@ func TestCommandWithNonCommand(t *testing.T) {
func TestCommandWithBotName(t *testing.T) {
message := tgbotapi.Message{Text: "/command@testbot"}
message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 16}}
if message.Command() != "command" {
t.Fail()
}
}
func TestCommandWithAtWithBotName(t *testing.T) {
message := tgbotapi.Message{Text: "/command@testbot"}
message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 16}}
if message.CommandWithAt() != "command@testbot" {
t.Fail()
}
}
func TestMessageCommandArgumentsWithArguments(t *testing.T) {
message := tgbotapi.Message{Text: "/command with arguments"}
message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
if message.CommandArguments() != "with arguments" {
t.Fail()
}
}
func TestMessageCommandArgumentsWithMalformedArguments(t *testing.T) {
message := tgbotapi.Message{Text: "/command-without argument space"}
message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
if message.CommandArguments() != "without argument space" {
t.Fail()
}
}
func TestMessageCommandArgumentsWithoutArguments(t *testing.T) {
message := tgbotapi.Message{Text: "/command"}
if message.CommandArguments() != "" {