From b24a37443a9122cc263f3f82eaeb6b687d65db76 Mon Sep 17 00:00:00 2001 From: Lord-Protector Date: Sat, 5 Aug 2017 12:29:06 +0300 Subject: [PATCH 01/22] Add PinChatMessage and UnpinChatMessage methods --- bot.go | 44 ++++++++++++++++++++++++++++++++++---------- configs.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 10 deletions(-) diff --git a/bot.go b/bot.go index 9d2adab..2d95f9e 100644 --- a/bot.go +++ b/bot.go @@ -837,18 +837,42 @@ 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) - } + 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) + resp, err := bot.MakeRequest("exportChatInviteLink", v) - var inviteLink string - err = json.Unmarshal(resp.Result, &inviteLink) + var inviteLink string + err = json.Unmarshal(resp.Result, &inviteLink) - return inviteLink, err + return inviteLink, err } + +// 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) +} + +// 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) +} \ No newline at end of file diff --git a/configs.go b/configs.go index d7358ee..c0293ce 100644 --- a/configs.go +++ b/configs.go @@ -1035,3 +1035,41 @@ 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 +} \ No newline at end of file From 3a60d28d73265c48475c2cf9f99b0842a88f1090 Mon Sep 17 00:00:00 2001 From: Lord-Protector Date: Sat, 5 Aug 2017 12:37:43 +0300 Subject: [PATCH 02/22] Tests for PinChatMessage and UnpinChatMessage methods --- bot_test.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/bot_test.go b/bot_test.go index d7cec08..0e45a38 100644 --- a/bot_test.go +++ b/bot_test.go @@ -609,3 +609,49 @@ func TestDeleteMessage(t *testing.T) { t.Fail() } } + +func TestPinChatMessage(t *testing.T) { + bot, _ := getBot(t) + + msg := tgbotapi.NewMessage(ChatID, "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(ChatID, "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() + } +} \ No newline at end of file From cd9a9741d0cfab359de3a9363b697721f41cc3d6 Mon Sep 17 00:00:00 2001 From: Lord-Protector Date: Sat, 5 Aug 2017 13:50:44 +0300 Subject: [PATCH 03/22] Create own supergroup with test already created bot and give it admin rights --- bot_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bot_test.go b/bot_test.go index 0e45a38..a811a27 100644 --- a/bot_test.go +++ b/bot_test.go @@ -14,6 +14,7 @@ import ( const ( TestToken = "153667468:AAHlSHlMqSt1f_uFmVRJbm5gntu2HI4WW8I" ChatID = 76918703 + SupergroupChatID = -1001120141283 ReplyToMessageID = 35 ExistingPhotoFileID = "AgADAgADw6cxG4zHKAkr42N7RwEN3IFShCoABHQwXEtVks4EH2wBAAEC" ExistingDocumentFileID = "BQADAgADOQADjMcoCcioX1GrDvp3Ag" @@ -613,7 +614,7 @@ func TestDeleteMessage(t *testing.T) { func TestPinChatMessage(t *testing.T) { bot, _ := getBot(t) - msg := tgbotapi.NewMessage(ChatID, "A test message from the test library in telegram-bot-api") + msg := tgbotapi.NewMessage(SupergroupChatID, "A test message from the test library in telegram-bot-api") msg.ParseMode = "markdown" message, _ := bot.Send(msg) @@ -633,7 +634,7 @@ func TestPinChatMessage(t *testing.T) { func TestUnpinChatMessage(t *testing.T) { bot, _ := getBot(t) - msg := tgbotapi.NewMessage(ChatID, "A test message from the test library in telegram-bot-api") + msg := tgbotapi.NewMessage(SupergroupChatID, "A test message from the test library in telegram-bot-api") msg.ParseMode = "markdown" message, _ := bot.Send(msg) From 5f5f94047c7a2f67e0d19bd5f1012321a0579e8a Mon Sep 17 00:00:00 2001 From: Henner Date: Sat, 7 Oct 2017 16:51:53 +0200 Subject: [PATCH 04/22] Rewrite message command methods to use entities Since the Telegram-API considers "/foo-bar" to be a command entity "/foo", we had choose a behaviour for the CommandArguments() method. The specification (https://core.telegram.org/bots#commands) mandates a space after the command name, so we decided to skip the first character following the command entity. However, Telegram clients render "/foo-bar" as if "/foo" was the command and "-bar" additional text, so this alternative should be remembered. --- types.go | 50 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/types.go b/types.go index 95231e4..91875bb 100644 --- a/types.go +++ b/types.go @@ -173,21 +173,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 +198,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 + } else { + return m.Text[entity.Length+1:] } - - return split[1] } // MessageEntity contains information about data in a Message. From c697a95948b08fd61419986d493601d14f3b3744 Mon Sep 17 00:00:00 2001 From: Henner Date: Sat, 7 Oct 2017 17:53:50 +0200 Subject: [PATCH 05/22] Fix failing tests --- types_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/types_test.go b/types_test.go index d2c24d4..ed8d625 100644 --- a/types_test.go +++ b/types_test.go @@ -34,6 +34,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 +59,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,6 +84,7 @@ 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() @@ -90,6 +93,7 @@ func TestCommandWithBotName(t *testing.T) { func TestMessageCommandArgumentsWithArguments(t *testing.T) { message := tgbotapi.Message{Text: "/command with arguments"} + message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}} if message.CommandArguments() != "with arguments" { t.Fail() } From b8425b053e0ac70e917076ef036661da382bd7c3 Mon Sep 17 00:00:00 2001 From: Henner Date: Sat, 7 Oct 2017 18:06:20 +0200 Subject: [PATCH 06/22] Add missing tests --- types_test.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/types_test.go b/types_test.go index ed8d625..9ef03b1 100644 --- a/types_test.go +++ b/types_test.go @@ -91,6 +91,15 @@ func TestCommandWithBotName(t *testing.T) { } } +func TestCommandWithAtWithBotName(t *testing.T) { + message := tgbotapi.Message{Text: "/command@testbot"} + message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 16}} + + 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}} @@ -99,6 +108,14 @@ func TestMessageCommandArgumentsWithArguments(t *testing.T) { } } +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() != "" { From 3cfc52c9c2c86b6df6a0b54d25c634abd9487cf4 Mon Sep 17 00:00:00 2001 From: zhuharev Date: Mon, 30 Oct 2017 00:03:48 +0300 Subject: [PATCH 07/22] print full response when debug --- bot.go | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/bot.go b/bot.go index 2d95f9e..2a69a33 100644 --- a/bot.go +++ b/bot.go @@ -7,6 +7,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "io/ioutil" "log" "net/http" @@ -67,6 +68,15 @@ func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse, } 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("%s %s", endpoint, bytes) + } + if resp.StatusCode == http.StatusForbidden { return APIResponse{}, errors.New(ErrAPIForbidden) } @@ -75,23 +85,32 @@ func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse, return APIResponse{}, errors.New(http.StatusText(resp.StatusCode)) } - bytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - return APIResponse{}, err + if !apiResp.Ok { + return apiResp, errors.New(apiResp.Description) } - if bot.Debug { - log.Println(endpoint, string(bytes)) + return apiResp, nil +} + +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 } // makeMessageRequest makes a request to a method that returns a Message. @@ -875,4 +894,4 @@ func (bot *BotAPI) UnpinChatMessage(config UnpinChatMessageConfig) (APIResponse, bot.debugLog(config.method(), v, nil) return bot.MakeRequest(config.method(), v) -} \ No newline at end of file +} From 6b5102fab5586322d6fe98112fc671ad23dfae33 Mon Sep 17 00:00:00 2001 From: zhuharev Date: Tue, 31 Oct 2017 02:34:34 +0300 Subject: [PATCH 08/22] fix debug printing --- bot.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bot.go b/bot.go index 2a69a33..7d94a2c 100644 --- a/bot.go +++ b/bot.go @@ -73,8 +73,9 @@ func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse, if err != nil { return apiResp, err } + if bot.Debug { - log.Printf("%s %s", endpoint, bytes) + log.Printf("%s resp: %s", endpoint, bytes) } if resp.StatusCode == http.StatusForbidden { @@ -110,7 +111,7 @@ func (bot *BotAPI) decodeAPIResponse(responseBody io.Reader, resp *APIResponse) return } - return + return data, nil } // makeMessageRequest makes a request to a method that returns a Message. From e6191af6795c37f1757d8ddba638ebb88e86c30b Mon Sep 17 00:00:00 2001 From: zhuharev Date: Tue, 7 Nov 2017 05:17:06 +0300 Subject: [PATCH 09/22] add doc message --- bot.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/bot.go b/bot.go index 7d94a2c..41e9b27 100644 --- a/bot.go +++ b/bot.go @@ -78,14 +78,6 @@ func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse, log.Printf("%s resp: %s", endpoint, bytes) } - if resp.StatusCode == http.StatusForbidden { - return APIResponse{}, errors.New(ErrAPIForbidden) - } - - if resp.StatusCode != http.StatusOK { - return APIResponse{}, errors.New(http.StatusText(resp.StatusCode)) - } - if !apiResp.Ok { return apiResp, errors.New(apiResp.Description) } @@ -93,6 +85,9 @@ func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse, 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) From bd1aa7e6ef60fd302ed84e1990fb4dbb791f73af Mon Sep 17 00:00:00 2001 From: Zaur Abdulgalimov Date: Thu, 9 Nov 2017 18:41:22 +0300 Subject: [PATCH 10/22] User:IsBot & RestrictChatMember:UntilDate --- bot.go | 3 +++ types.go | 1 + 2 files changed, 4 insertions(+) diff --git a/bot.go b/bot.go index 41e9b27..030612f 100644 --- a/bot.go +++ b/bot.go @@ -739,6 +739,9 @@ func (bot *BotAPI) RestrictChatMember(config RestrictChatMemberConfig) (APIRespo 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) diff --git a/types.go b/types.go index 91875bb..bef68b8 100644 --- a/types.go +++ b/types.go @@ -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. From 5dbfec17f3f32cc28060221a35a9af6d93739b8a Mon Sep 17 00:00:00 2001 From: Zaur Abdulgalimov Date: Thu, 9 Nov 2017 19:46:11 +0300 Subject: [PATCH 11/22] test User:IsBot --- types_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types_test.go b/types_test.go index 9ef03b1..62089c4 100644 --- a/types_test.go +++ b/types_test.go @@ -8,7 +8,7 @@ import ( ) func TestUserStringWith(t *testing.T) { - user := tgbotapi.User{0, "Test", "Test", "", "en"} + user := tgbotapi.User{0, "Test", "Test", "", "en", false} if user.String() != "Test Test" { t.Fail() From 2022d04b94f50056a09962b1ac81cdd821d20a55 Mon Sep 17 00:00:00 2001 From: zhuharev Date: Thu, 9 Nov 2017 21:51:50 +0300 Subject: [PATCH 12/22] fix tests, linter errors and fmt --- bot.go | 8 ++++++-- bot_test.go | 12 ++++++------ types_test.go | 17 +++++++++++++++-- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/bot.go b/bot.go index 030612f..e201944 100644 --- a/bot.go +++ b/bot.go @@ -748,6 +748,7 @@ func (bot *BotAPI) RestrictChatMember(config RestrictChatMemberConfig) (APIRespo return bot.MakeRequest("restrictChatMember", v) } +// PromoteChatMember add admin rights to user func (bot *BotAPI) PromoteChatMember(config PromoteChatMemberConfig) (APIResponse, error) { v := url.Values{} @@ -864,6 +865,9 @@ func (bot *BotAPI) GetInviteLink(config ChatConfig) (string, error) { } resp, err := bot.MakeRequest("exportChatInviteLink", v) + if err != nil { + return "", err + } var inviteLink string err = json.Unmarshal(resp.Result, &inviteLink) @@ -871,7 +875,7 @@ func (bot *BotAPI) GetInviteLink(config ChatConfig) (string, error) { return inviteLink, err } -// Pin message in supergroup +// PinChatMessage pin message in supergroup func (bot *BotAPI) PinChatMessage(config PinChatMessageConfig) (APIResponse, error) { v, err := config.values() if err != nil { @@ -883,7 +887,7 @@ func (bot *BotAPI) PinChatMessage(config PinChatMessageConfig) (APIResponse, err return bot.MakeRequest(config.method(), v) } -// Unpin message in supergroup +// UnpinChatMessage unpin message in supergroup func (bot *BotAPI) UnpinChatMessage(config UnpinChatMessageConfig) (APIResponse, error) { v, err := config.values() if err != nil { diff --git a/bot_test.go b/bot_test.go index a811a27..0fb0855 100644 --- a/bot_test.go +++ b/bot_test.go @@ -619,8 +619,8 @@ func TestPinChatMessage(t *testing.T) { message, _ := bot.Send(msg) pinChatMessageConfig := tgbotapi.PinChatMessageConfig{ - ChatID: message.Chat.ID, - MessageID: message.MessageID, + ChatID: message.Chat.ID, + MessageID: message.MessageID, DisableNotification: false, } _, err := bot.PinChatMessage(pinChatMessageConfig) @@ -640,14 +640,14 @@ func TestUnpinChatMessage(t *testing.T) { // We need pin message to unpin something pinChatMessageConfig := tgbotapi.PinChatMessageConfig{ - ChatID: message.Chat.ID, - MessageID: message.MessageID, + ChatID: message.Chat.ID, + MessageID: message.MessageID, DisableNotification: false, } _, err := bot.PinChatMessage(pinChatMessageConfig) unpinChatMessageConfig := tgbotapi.UnpinChatMessageConfig{ - ChatID: message.Chat.ID, + ChatID: message.Chat.ID, } _, err = bot.UnpinChatMessage(unpinChatMessageConfig) @@ -655,4 +655,4 @@ func TestUnpinChatMessage(t *testing.T) { t.Error(err) t.Fail() } -} \ No newline at end of file +} diff --git a/types_test.go b/types_test.go index 62089c4..bb7bb64 100644 --- a/types_test.go +++ b/types_test.go @@ -8,7 +8,14 @@ import ( ) func TestUserStringWith(t *testing.T) { - user := tgbotapi.User{0, "Test", "Test", "", "en", false} + 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() From 9b7184fa7951958c72a6a0fcc1e456065bc829fa Mon Sep 17 00:00:00 2001 From: scnace Date: Mon, 8 Jan 2018 02:10:50 +0800 Subject: [PATCH 13/22] check telegram webhook tls handshake --- README.md | 8 +++++++- bot_test.go | 24 +++++++++++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 266f4ed..d9a6873 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/bot_test.go b/bot_test.go index 0fb0855..5a3057d 100644 --- a/bot_test.go +++ b/bot_test.go @@ -467,7 +467,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() } @@ -484,7 +490,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() } @@ -549,7 +561,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) From 3b0c0317300ecb967352a7ca2aa7a42a8822ab50 Mon Sep 17 00:00:00 2001 From: Lim Ming Wei Date: Tue, 9 Jan 2018 11:51:17 +0800 Subject: [PATCH 14/22] minor typo in helpers.go --- helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers.go b/helpers.go index 132d957..f5800f4 100644 --- a/helpers.go +++ b/helpers.go @@ -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}, From f8145e3a68e7bf0bc5ddb308709ddbb839606422 Mon Sep 17 00:00:00 2001 From: Robert Vollmert Date: Thu, 11 Jan 2018 22:41:45 +0100 Subject: [PATCH 15/22] reply_markup is optional for InlineQueryResultGame --- types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types.go b/types.go index bef68b8..4a58287 100644 --- a/types.go +++ b/types.go @@ -657,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 From 9e8d16e1a8527e9fbb76b2f40a221d3a244055c4 Mon Sep 17 00:00:00 2001 From: Robert Vollmert Date: Fri, 12 Jan 2018 12:12:32 +0100 Subject: [PATCH 16/22] make ChatID int64 for SetGameScoreConfig It's int64 everywhere else. --- configs.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configs.go b/configs.go index c0293ce..6c12d64 100644 --- a/configs.go +++ b/configs.go @@ -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) } From 6e69f99d113a3ac6537961e7ea3e1b1160f688eb Mon Sep 17 00:00:00 2001 From: Oleksandr Savchuk Date: Sat, 3 Mar 2018 20:20:03 +0200 Subject: [PATCH 17/22] add setChatTitle and setChatDescription methods --- bot.go | 24 ++++++++++++++++++++++++ configs.go | 44 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/bot.go b/bot.go index e201944..48d729b 100644 --- a/bot.go +++ b/bot.go @@ -898,3 +898,27 @@ func (bot *BotAPI) UnpinChatMessage(config UnpinChatMessageConfig) (APIResponse, 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) +} diff --git a/configs.go b/configs.go index 6c12d64..e16cbbb 100644 --- a/configs.go +++ b/configs.go @@ -1038,8 +1038,8 @@ func (config DeleteMessageConfig) values() (url.Values, error) { // PinChatMessageConfig contains information of a message in a chat to pin. type PinChatMessageConfig struct { - ChatID int64 - MessageID int + ChatID int64 + MessageID int DisableNotification bool } @@ -1072,4 +1072,42 @@ func (config UnpinChatMessageConfig) values() (url.Values, error) { v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) return v, nil -} \ No newline at end of file +} + +// 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 +} From 04f51c32513f07bbb8f8392715d5524040e2671a Mon Sep 17 00:00:00 2001 From: Behrang Noruzi Niya Date: Sun, 4 Mar 2018 14:40:17 +0330 Subject: [PATCH 18/22] Add response parameters to error messages --- bot.go | 6 +++++- types.go | 11 ++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/bot.go b/bot.go index e201944..a0e1f81 100644 --- a/bot.go +++ b/bot.go @@ -79,7 +79,11 @@ func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse, } if !apiResp.Ok { - return apiResp, errors.New(apiResp.Description) + parameters := ResponseParameters{} + if apiResp.Parameters != nil { + parameters = *apiResp.Parameters + } + return apiResp, Error{apiResp.Description, parameters} } return apiResp, nil diff --git a/types.go b/types.go index 4a58287..38bc0f6 100644 --- a/types.go +++ b/types.go @@ -410,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 @@ -771,3 +771,12 @@ type PreCheckoutQuery struct { ShippingOptionID string `json:"shipping_option_id,omitempty"` OrderInfo *OrderInfo `json:"order_info,omitempty"` } + +type Error struct { + Message string + ResponseParameters +} + +func (e Error) Error() string { + return e.Message +} From 57be98801107f19622d22a718cdc4a5f52cc0712 Mon Sep 17 00:00:00 2001 From: Oleksandr Savchuk Date: Mon, 5 Mar 2018 17:04:37 +0200 Subject: [PATCH 19/22] add setChatPhoto method --- bot.go | 12 ++++++++++++ configs.go | 15 +++++++++++++++ helpers.go | 31 +++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/bot.go b/bot.go index 48d729b..165d3b6 100644 --- a/bot.go +++ b/bot.go @@ -922,3 +922,15 @@ func (bot *BotAPI) SetChatDescription(config SetChatDescriptionConfig) (APIRespo 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 + } + + file := config.getFile() + + return bot.UploadFile(config.method(), params, config.name(), file) +} diff --git a/configs.go b/configs.go index e16cbbb..1bbaff4 100644 --- a/configs.go +++ b/configs.go @@ -1111,3 +1111,18 @@ func (config SetChatDescriptionConfig) values() (url.Values, error) { 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" +} diff --git a/helpers.go b/helpers.go index f5800f4..c23a3bf 100644 --- a/helpers.go +++ b/helpers.go @@ -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, + }, + } +} From a36af7a672af72fb2202924ab5c9001e17b76464 Mon Sep 17 00:00:00 2001 From: Oleksandr Savchuk Date: Mon, 5 Mar 2018 17:12:56 +0200 Subject: [PATCH 20/22] add deleteChatPhoto method --- bot.go | 12 ++++++++++++ configs.go | 17 +++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/bot.go b/bot.go index 165d3b6..ee1ddd0 100644 --- a/bot.go +++ b/bot.go @@ -934,3 +934,15 @@ func (bot *BotAPI) SetChatPhoto(config SetChatPhotoConfig) (APIResponse, error) return bot.UploadFile(config.method(), params, config.name(), file) } + +// DeleteChatPhoto delete photo of chat. +func (bot *BotAPI) DeleteChatPhoto(config DeleteChatPhotoConfig) (APIResponse, error) { + v, err := config.values() + if err != nil { + return APIResponse{}, err + } + + bot.debugLog(config.method(), v, nil) + + return bot.MakeRequest(config.method(), v) +} diff --git a/configs.go b/configs.go index 1bbaff4..574b3dd 100644 --- a/configs.go +++ b/configs.go @@ -1126,3 +1126,20 @@ func (config SetChatPhotoConfig) name() string { 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 +} From 309f2dd87fd1a17c552d9ae11a983ff28fc4afdf Mon Sep 17 00:00:00 2001 From: Syfaro Date: Mon, 26 Mar 2018 11:54:02 -0500 Subject: [PATCH 21/22] Minor code quality improvements. --- bot_test.go | 12 +++++++++--- types.go | 5 +++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/bot_test.go b/bot_test.go index 5a3057d..e7aa7ac 100644 --- a/bot_test.go +++ b/bot_test.go @@ -373,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 { @@ -386,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) @@ -399,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) diff --git a/types.go b/types.go index 38bc0f6..d3d30dc 100644 --- a/types.go +++ b/types.go @@ -232,9 +232,9 @@ func (m *Message) CommandArguments() string { entity := (*m.Entities)[0] if len(m.Text) == entity.Length { return "" // The command makes up the whole message - } else { - return m.Text[entity.Length+1:] } + + return m.Text[entity.Length+1:] } // MessageEntity contains information about data in a Message. @@ -772,6 +772,7 @@ type PreCheckoutQuery struct { OrderInfo *OrderInfo `json:"order_info,omitempty"` } +// Error is an error containing extra information returned by the Telegram API. type Error struct { Message string ResponseParameters From 0e0af0c480ea98e982d5f4d45fb39577c6ab1e3e Mon Sep 17 00:00:00 2001 From: Kirill Zhuharev Date: Wed, 28 Mar 2018 16:10:29 +0300 Subject: [PATCH 22/22] fix #159 --- bot.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/bot.go b/bot.go index 1a3b7fd..8fb6200 100644 --- a/bot.go +++ b/bot.go @@ -731,16 +731,16 @@ 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 { @@ -765,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)) }