diff --git a/README.md b/README.md index 0520f27..2d0fce8 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ something with plugins and command handlers without having to design all that yourself. Use `github.com/go-telegram-bot-api/telegram-bot-api` for the latest -version, or use `gopkg.in/telegram-bot-api.v1` for the stable build. +version, or use `gopkg.in/telegram-bot-api.v2` for the stable build. ## Example @@ -29,7 +29,7 @@ package main import ( "log" - "gopkg.in/telegram-bot-api.v1" + "gopkg.in/telegram-bot-api.v2" ) func main() { @@ -65,7 +65,7 @@ you may use a slightly different method. package main import ( - "gopkg.in/telegram-bot-api.v1" + "gopkg.in/telegram-bot-api.v2" "log" "net/http" ) @@ -85,7 +85,7 @@ func main() { log.Fatal(err) } - updates, _ := bot.ListenForWebhook("/" + bot.Token) + updates := bot.ListenForWebhook("/" + bot.Token) go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil) for update := range updates { diff --git a/bot.go b/bot.go index 72b5849..9eaad74 100644 --- a/bot.go +++ b/bot.go @@ -135,7 +135,7 @@ func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse, defer resp.Body.Close() if resp.StatusCode == http.StatusForbidden { - return APIResponse{}, errors.New(APIForbidden) + return APIResponse{}, errors.New(ErrAPIForbidden) } bytes, err := ioutil.ReadAll(resp.Body) @@ -217,7 +217,7 @@ func (bot *BotAPI) UploadFile(endpoint string, params map[string]string, fieldna ms.WriteReader(fieldname, f.Name, int64(len(data)), buf) default: - return APIResponse{}, errors.New("bad file type") + return APIResponse{}, errors.New(ErrBadFileType) } method := fmt.Sprintf(APIEndpoint, bot.Token, endpoint) @@ -523,10 +523,10 @@ func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (<-chan Update, error) { } // ListenForWebhook registers a http handler for a webhook. -func (bot *BotAPI) ListenForWebhook(pattern string) (<-chan Update, http.Handler) { +func (bot *BotAPI) ListenForWebhook(pattern string) <-chan Update { updatesChan := make(chan Update, 100) - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) { bytes, _ := ioutil.ReadAll(r.Body) var update Update @@ -535,9 +535,7 @@ func (bot *BotAPI) ListenForWebhook(pattern string) (<-chan Update, http.Handler updatesChan <- update }) - http.HandleFunc(pattern, handler) - - return updatesChan, handler + return updatesChan } // AnswerInlineQuery sends a response to an inline query. @@ -556,5 +554,7 @@ func (bot *BotAPI) AnswerInlineQuery(config InlineConfig) (APIResponse, error) { } v.Add("results", string(data)) + bot.debugLog("answerInlineQuery", v, nil) + return bot.MakeRequest("answerInlineQuery", v) } diff --git a/bot_test.go b/bot_test.go index e61c87c..215e08b 100644 --- a/bot_test.go +++ b/bot_test.go @@ -5,9 +5,7 @@ import ( "io/ioutil" "log" "net/http" - "net/http/httptest" "os" - "strings" "testing" ) @@ -351,20 +349,6 @@ func TestGetUserProfilePhotos(t *testing.T) { } } -func TestListenForWebhook(t *testing.T) { - bot, _ := getBot(t) - - _, handler := bot.ListenForWebhook("/") - - req, _ := http.NewRequest("GET", "", strings.NewReader("{}")) - w := httptest.NewRecorder() - - handler.ServeHTTP(w, req) - if w.Code != http.StatusOK { - t.Errorf("Home page didn't return %v", http.StatusOK) - } -} - func TestSetWebhookWithCert(t *testing.T) { bot, _ := getBot(t) @@ -445,10 +429,44 @@ func ExampleNewWebhook() { log.Fatal(err) } - updates, _ := bot.ListenForWebhook("/" + bot.Token) + 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) } } + +func ExampleAnswerInlineQuery() { + bot, err := tgbotapi.NewBotAPI("MyAwesomeBotToken") // create new bot + if err != nil { + log.Panic(err) + } + + log.Printf("Authorized on account %s", bot.Self.UserName) + + u := tgbotapi.NewUpdate(0) + u.Timeout = 60 + + updates, err := bot.GetUpdatesChan(u) + + for update := range updates { + if update.InlineQuery.Query == "" { // if no inline query, ignore it + continue + } + + article := tgbotapi.NewInlineQueryResultArticle(update.InlineQuery.ID, "Echo", update.InlineQuery.Query) + article.Description = update.InlineQuery.Query + + inlineConf := tgbotapi.InlineConfig{ + InlineQueryID: update.InlineQuery.ID, + IsPersonal: true, + CacheTime: 0, + Results: []interface{}{article}, + } + + if _, err := bot.AnswerInlineQuery(inlineConf); err != nil { + log.Println(err) + } + } +} diff --git a/configs.go b/configs.go index d7b146a..1839a5d 100644 --- a/configs.go +++ b/configs.go @@ -3,7 +3,6 @@ package tgbotapi import ( "encoding/json" "io" - "log" "net/url" "strconv" ) @@ -31,13 +30,20 @@ const ( // API errors const ( - // APIForbidden happens when a token is bad - APIForbidden = "forbidden" + // ErrAPIForbidden happens when a token is bad + ErrAPIForbidden = "forbidden" ) // Constant values for ParseMode in MessageConfig const ( ModeMarkdown = "Markdown" + ModeHTML = "HTML" +) + +// Library errors +const ( + // ErrBadFileType happens when you pass an unknown type + ErrBadFileType = "bad file type" ) // Chattable is any config type that can be sent. @@ -57,10 +63,11 @@ type Fileable interface { // BaseChat is base type for all chat config types. type BaseChat struct { - ChatID int // required - ChannelUsername string - ReplyToMessageID int - ReplyMarkup interface{} + ChatID int // required + ChannelUsername string + ReplyToMessageID int + ReplyMarkup interface{} + DisableNotification bool } // values returns url.Values representation of BaseChat @@ -85,13 +92,14 @@ func (chat *BaseChat) values() (url.Values, error) { v.Add("reply_markup", string(data)) } + v.Add("disable_notification", strconv.FormatBool(chat.DisableNotification)) + return v, nil } // BaseFile is a base type for all file config types. type BaseFile struct { BaseChat - FilePath string File interface{} FileID string UseExisting bool @@ -130,21 +138,14 @@ func (file BaseFile) params() (map[string]string, error) { params["file_size"] = strconv.Itoa(file.FileSize) } + params["disable_notification"] = strconv.FormatBool(file.DisableNotification) + return params, nil } // getFile returns the file. func (file BaseFile) getFile() interface{} { - var result interface{} - if file.FilePath == "" { - result = file.File - } else { - log.Println("FilePath is deprecated.") - log.Println("Please use BaseFile.File instead.") - result = file.FilePath - } - - return result + return file.File } // useExistingFile returns if the BaseFile has already been uploaded. @@ -515,9 +516,9 @@ type FileReader struct { // InlineConfig contains information on making an InlineQuery response. type InlineConfig struct { - InlineQueryID string `json:"inline_query_id"` - Results []InlineQueryResult `json:"results"` - CacheTime int `json:"cache_time"` - IsPersonal bool `json:"is_personal"` - NextOffset string `json:"next_offset"` + InlineQueryID string `json:"inline_query_id"` + Results []interface{} `json:"results"` + CacheTime int `json:"cache_time"` + IsPersonal bool `json:"is_personal"` + NextOffset string `json:"next_offset"` } diff --git a/helpers.go b/helpers.go index bb9dc1a..6dcc91a 100644 --- a/helpers.go +++ b/helpers.go @@ -281,3 +281,49 @@ func NewWebhookWithCert(link string, file interface{}) WebhookConfig { Certificate: file, } } + +// NewInlineQueryResultArticle creates a new inline query article. +func NewInlineQueryResultArticle(id, title, messageText string) InlineQueryResultArticle { + return InlineQueryResultArticle{ + Type: "article", + ID: id, + Title: title, + MessageText: messageText, + } +} + +// NewInlineQueryResultGIF creates a new inline query GIF. +func NewInlineQueryResultGIF(id, url string) InlineQueryResultGIF { + return InlineQueryResultGIF{ + Type: "gif", + ID: id, + URL: url, + } +} + +// NewInlineQueryResultMPEG4GIF creates a new inline query MPEG4 GIF. +func NewInlineQueryResultMPEG4GIF(id, url string) InlineQueryResultMPEG4GIF { + return InlineQueryResultMPEG4GIF{ + Type: "mpeg4_gif", + ID: id, + URL: url, + } +} + +// NewInlineQueryResultPhoto creates a new inline query photo. +func NewInlineQueryResultPhoto(id, url string) InlineQueryResultPhoto { + return InlineQueryResultPhoto{ + Type: "photo", + ID: id, + URL: url, + } +} + +// NewInlineQueryResultVideo creates a new inline query video. +func NewInlineQueryResultVideo(id, url string) InlineQueryResultVideo { + return InlineQueryResultVideo{ + Type: "video", + ID: id, + URL: url, + } +} diff --git a/types.go b/types.go index c9b7a8f..430bea3 100644 --- a/types.go +++ b/types.go @@ -3,7 +3,6 @@ package tgbotapi import ( "encoding/json" "fmt" - "log" "strings" "time" ) @@ -147,15 +146,6 @@ func (m *Message) Time() time.Time { return time.Unix(int64(m.Date), 0) } -// IsGroup returns if the message was sent to a group. -// -// Deprecated in favor of Chat.IsGroup. -func (m *Message) IsGroup() bool { - log.Println("Message.IsGroup is deprecated.") - log.Println("Please use Chat.IsGroup instead.") - return m.Chat.IsGroup() -} - // IsCommand returns true if message starts with '/'. func (m *Message) IsCommand() bool { return m.Text != "" && m.Text[0] == '/' @@ -163,11 +153,13 @@ func (m *Message) IsCommand() bool { // 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. func (m *Message) Command() string { if !m.IsCommand() { return "" } - command := strings.SplitN(m.Text, " ", 2)[0] + command := strings.SplitN(m.Text, " ", 2)[0][1:] if i := strings.Index(command, "@"); i != -1 { command = command[:i] @@ -263,7 +255,7 @@ type Location struct { Latitude float32 `json:"latitude"` } -// UserProfilePhotos contains information a set of user profile photos. +// UserProfilePhotos contains a set of user profile photos. type UserProfilePhotos struct { TotalCount int `json:"total_count"` Photos []PhotoSize `json:"photos"` @@ -307,20 +299,15 @@ type ForceReply struct { // InlineQuery is a Query from Telegram for an inline request. type InlineQuery struct { ID string `json:"id"` - From User `json:"user"` + From User `json:"from"` Query string `json:"query"` Offset string `json:"offset"` } -// InlineQueryResult is the base type that all InlineQuery Results have. -type InlineQueryResult struct { - Type string `json:"type"` // required - ID string `json:"id"` // required -} - // InlineQueryResultArticle is an inline query response article. type InlineQueryResultArticle struct { - InlineQueryResult + Type string `json:"type"` // required + ID string `json:"id"` // required Title string `json:"title"` // required MessageText string `json:"message_text"` // required ParseMode string `json:"parse_mode"` @@ -335,7 +322,8 @@ type InlineQueryResultArticle struct { // InlineQueryResultPhoto is an inline query response photo. type InlineQueryResultPhoto struct { - InlineQueryResult + Type string `json:"type"` // required + ID string `json:"id"` // required URL string `json:"photo_url"` // required MimeType string `json:"mime_type"` Width int `json:"photo_width"` @@ -351,7 +339,8 @@ type InlineQueryResultPhoto struct { // InlineQueryResultGIF is an inline query response GIF. type InlineQueryResultGIF struct { - InlineQueryResult + Type string `json:"type"` // required + ID string `json:"id"` // required URL string `json:"gif_url"` // required Width int `json:"gif_width"` Height int `json:"gif_height"` @@ -365,7 +354,8 @@ type InlineQueryResultGIF struct { // InlineQueryResultMPEG4GIF is an inline query response MPEG4 GIF. type InlineQueryResultMPEG4GIF struct { - InlineQueryResult + Type string `json:"type"` // required + ID string `json:"id"` // required URL string `json:"mpeg4_url"` // required Width int `json:"mpeg4_width"` Height int `json:"mpeg4_height"` @@ -379,7 +369,8 @@ type InlineQueryResultMPEG4GIF struct { // InlineQueryResultVideo is an inline query response video. type InlineQueryResultVideo struct { - InlineQueryResult + Type string `json:"type"` // required + ID string `json:"id"` // required URL string `json:"video_url"` // required MimeType string `json:"mime_type"` // required MessageText string `json:"message_text"` // required diff --git a/types_test.go b/types_test.go index 8e10e22..6588251 100644 --- a/types_test.go +++ b/types_test.go @@ -58,7 +58,7 @@ func TestIsCommandWithEmptyText(t *testing.T) { func TestCommandWithCommand(t *testing.T) { message := tgbotapi.Message{Text: "/command"} - if message.Command() != "/command" { + if message.Command() != "command" { t.Fail() } } @@ -79,6 +79,14 @@ func TestCommandWithNonCommand(t *testing.T) { } } +func TestCommandWithBotName(t *testing.T) { + message := tgbotapi.Message{Text: "/command@testbot"} + + if message.Command() != "command" { + t.Fail() + } +} + func TestMessageCommandArgumentsWithArguments(t *testing.T) { message := tgbotapi.Message{Text: "/command with arguments"} if message.CommandArguments() != "with arguments" {