From 276daa07328d684c5ed7167816885d2a6296f879 Mon Sep 17 00:00:00 2001 From: Ian Byrd Date: Thu, 10 Nov 2016 21:31:32 +0200 Subject: [PATCH 1/3] README.md bikeshedding. --- README.md | 175 +++++++++++++++++++++++++++--------------------------- 1 file changed, 86 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index ba2ff46..d781bd4 100644 --- a/README.md +++ b/README.md @@ -1,134 +1,131 @@ # Telebot ->Telebot is a convenient wrapper to Telegram Bots API, written in Golang. +>Telebot is a Telegram bot framework in Go. [![GoDoc](https://godoc.org/github.com/tucnak/telebot?status.svg)](https://godoc.org/github.com/tucnak/telebot) [![Travis](https://travis-ci.org/tucnak/telebot.svg?branch=master)](https://travis-ci.org/tucnak/telebot) -Bots are special Telegram accounts designed to handle messages automatically. Users can interact with bots by sending them command messages in private or group chats. These accounts serve as an interface for code running somewhere on your server. +Bots are special Telegram accounts designed to handle messages automatically. Users can interact with bots +by sending them command messages in private or group chats. These accounts serve as an interface for +code running somewhere on your server. Telebot offers a convenient wrapper to Bots API, so you shouldn't even -care about networking at all. You may install it with +bother about networking at all. You may install it with go get github.com/tucnak/telebot (after setting up your `GOPATH` properly). -Since you are probably -hosting your bot in a public repository, we'll add an environment -variable for the token in this example. Please set it with +We highly recommend you to keep your bot access token outside the code base, +preferably as an environmental variable: export BOT_TOKEN= - -Here is an example "helloworld" bot, written with telebot: - +Take a look at a minimal functional bot setup: ```go package main import ( - "log" - "time" - "os" - "github.com/tucnak/telebot" + "log" + "os" + "time" + + "github.com/tucnak/telebot" ) func main() { - bot, err := telebot.NewBot(os.Getenv("BOT_TOKEN")) - if err != nil { - log.Fatalln(err) - } - - messages := make(chan telebot.Message) - bot.Listen(messages, 1*time.Second) - - for message := range messages { - if message.Text == "/hi" { - bot.SendMessage(message.Chat, - "Hello, "+message.Sender.FirstName+"!", nil) - } - } + bot, err := telebot.NewBot(os.Getenv("BOT_TOKEN")) + if err != nil { + log.Fatalln(err) + } + + messages := make(chan telebot.Message, 100) + bot.Listen(messages, 1*time.Second) + + for message := range messages { + if message.Text == "/hi" { + bot.SendMessage(message.Chat, + "Hello, "+message.Sender.FirstName+"!", nil) + } + } } ``` ## Inline mode - -As of January 4, 2016, Telegram added inline mode support for bots. -Telebot support inline mode in a fancy manner. Here's a nice way to handle both incoming messages and inline queries: +As of January 4, 2016, Telegram added inline mode support for bots. Here's +a nice way to handle both incoming messages and inline queries in the meantime: ```go package main import ( - "log" - "time" - "os" - "github.com/tucnak/telebot" + "log" + "time" + "os" + "github.com/tucnak/telebot" ) -var bot *telebot.Bot - func main() { - var err error - bot, err = telebot.NewBot(os.Getenv("BOT_TOKEN")) - if err != nil { - log.Fatalln(err) - } + bot, err := telebot.NewBot(os.Getenv("BOT_TOKEN")) + if err != nil { + log.Fatalln(err) + } - bot.Messages = make(chan telebot.Message, 1000) - bot.Queries = make(chan telebot.Query, 1000) + bot.Messages = make(chan telebot.Message, 100) + bot.Queries = make(chan telebot.Query, 1000) - go messages() - go queries() + go messages(bot) + go queries(bot) - bot.Start(1 * time.Second) + bot.Start(1 * time.Second) } -func messages() { - for message := range bot.Messages { - log.Printf("Received a message from %s with the text: %s\n", message.Sender.Username, message.Text) - } +func messages(bot *telebot.Bot) { + for message := range bot.Messages { + log.Printf("Received a message from %s with the text: %s\n", + message.Sender.Username, message.Text) + } } -func queries() { - for query := range bot.Queries { - log.Println("--- new query ---") - log.Println("from:", query.From.Username) - log.Println("text:", query.Text) - - // Create an article (a link) object to show in our results. - article := &telebot.InlineQueryResultArticle{ - Title: "Telegram bot framework written in Go", - URL: "https://github.com/tucnak/telebot", - InputMessageContent: &telebot.InputTextMessageContent{ - Text: "Telebot is a convenient wrapper to Telegram Bots API, written in Golang.", - DisablePreview: false, - }, - } - - // Build the list of results. In this instance, just our 1 article from above. - results := []telebot.InlineQueryResult{article} - - // Build a response object to answer the query. - response := telebot.QueryResponse{ - Results: results, - IsPersonal: true, - } - - // And finally send the response. - if err := bot.AnswerInlineQuery(&query, &response); err != nil { - log.Println("Failed to respond to query:", err) - } - } +func queries(bot *telebot.Bot) { + for query := range bot.Queries { + log.Println("--- new query ---") + log.Println("from:", query.From.Username) + log.Println("text:", query.Text) + + // Create an article (a link) object to show in results. + article := &telebot.InlineQueryResultArticle{ + Title: "Telebot", + URL: "https://github.com/tucnak/telebot", + InputMessageContent: &telebot.InputTextMessageContent{ + Text: "Telebot is a Telegram bot framework.", + DisablePreview: false, + }, + } + + // Build the list of results (make sure to pass pointers!). + results := []telebot.InlineQueryResult{article} + + // Build a response object to answer the query. + response := telebot.QueryResponse{ + Results: results, + IsPersonal: true, + } + + // Send it. + if err := bot.AnswerInlineQuery(&query, &response); err != nil { + log.Println("Failed to respond to query:", err) + } + } } ``` ## Files - Telebot lets you upload files from the file system: + ```go boom, err := telebot.NewFile("boom.ogg") if err != nil { - return err + return err } audio := telebot.Audio{File: boom} @@ -139,22 +136,22 @@ err = bot.SendAudio(recipient, &audio, nil) ``` ## Reply markup - Sometimes you wanna send a little complicated messages with some optional parameters. The third argument of all `Send*` methods accepts `telebot.SendOptions`, capable of defining an advanced reply markup: + ```go // Send a selective force reply message. bot.SendMessage(user, "pong", &telebot.SendOptions{ - ReplyMarkup: telebot.ReplyMarkup{ - ForceReply: true, - Selective: true, - + ReplyMarkup: telebot.ReplyMarkup{ + ForceReply: true, + Selective: true, CustomKeyboard: [][]string{ + []string{"1", "2", "3"}, []string{"4", "5", "6"}, []string{"7", "8", "9"}, []string{"*", "0", "#"}, }, - }, - }, + }, + }, ) ``` From d8b2ca1983a9fba56d70e3b5dd249eae15d12dad Mon Sep 17 00:00:00 2001 From: Ian Byrd Date: Thu, 10 Nov 2016 21:34:02 +0200 Subject: [PATCH 2/3] Fixes lots of complete bollocks that got into the codebase. - Malformed JSON field tags, OMG fixed - Making linters happy - Foursquare_id -> FoursquareID, fuck BC --- bot.go | 14 +++++++------- input_types.go | 2 +- message.go | 6 +++--- types.go | 12 ++++++------ 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/bot.go b/bot.go index 08b592c..0f8603c 100644 --- a/bot.go +++ b/bot.go @@ -49,7 +49,7 @@ func (b *Bot) poll( callbacks chan Callback, timeout time.Duration, ) { - var latestUpdate int64 = 0 + var latestUpdate int64 for { updates, err := getUpdates(b.Token, @@ -462,8 +462,8 @@ func (b *Bot) SendVenue(recipient Recipient, venue *Venue, options *SendOptions) "longitude": fmt.Sprintf("%f", venue.Location.Longitude), "title": venue.Title, "address": venue.Address} - if venue.Foursquare_id != "" { - params["foursquare_id"] = venue.Foursquare_id + if venue.FoursquareID != "" { + params["foursquare_id"] = venue.FoursquareID } if options != nil { @@ -733,7 +733,7 @@ func (b *Bot) GetChatAdministrators(recipient Recipient) ([]ChatMember, error) { var responseRecieved struct { Ok bool Result []ChatMember - Description string `json:"description",omitempty` + Description string `json:"description"` } err = json.Unmarshal(responseJSON, &responseRecieved) @@ -763,7 +763,7 @@ func (b *Bot) GetChatMembersCount(recipient Recipient) (int, error) { var responseRecieved struct { Ok bool Result int - Description string `json:"description",omitempty` + Description string `json:"description"` } err = json.Unmarshal(responseJSON, &responseRecieved) @@ -793,7 +793,7 @@ func (b *Bot) GetUserProfilePhotos(recipient Recipient) (UserProfilePhotos, erro var responseRecieved struct { Ok bool Result UserProfilePhotos - Description string `json:"description",omitempty` + Description string `json:"description"` } err = json.Unmarshal(responseJSON, &responseRecieved) @@ -824,7 +824,7 @@ func (b *Bot) GetChatMember(recipient Recipient, user User) (ChatMember, error) var responseRecieved struct { Ok bool Result ChatMember - Description string `json:"description",omitempty` + Description string `json:"description"` } err = json.Unmarshal(responseJSON, &responseRecieved) diff --git a/input_types.go b/input_types.go index abb1c41..30c79b0 100644 --- a/input_types.go +++ b/input_types.go @@ -19,7 +19,7 @@ type InputTextMessageContent struct { ParseMode string `json:"parse_mode,omitempty"` // Optional. Disables link previews for links in the sent message. - DisablePreview bool `json:"disable_web_page_preview` + DisablePreview bool `json:"disable_web_page_preview"` } func (input *InputTextMessageContent) IsInputMessageContent() bool { diff --git a/message.go b/message.go index 19e3cd4..dff4c93 100644 --- a/message.go +++ b/message.go @@ -129,9 +129,9 @@ type Message struct { // Sender would lead to creator of the migration. MigrateFrom int64 `json:"migrate_from_chat_id"` - Entities []MessageEntity `json:"entities",omitempty` - - Caption string `json:"caption",omitempty` + Entities []MessageEntity `json:"entities,omitempty"` + + Caption string `json:"caption,omitempty"` } // Origin returns an origin of message: group chat / personal. diff --git a/types.go b/types.go index f806624..cdff896 100644 --- a/types.go +++ b/types.go @@ -210,10 +210,10 @@ type CallbackResponse struct { // Venue object represents a venue location with name, address and optional foursquare id. type Venue struct { - Location Location `json:"location"` - Title string `json:"title"` - Address string `json:"address"` - Foursquare_id string `json:"foursquare_id",omitempty` + Location Location `json:"location"` + Title string `json:"title"` + Address string `json:"address"` + FoursquareID string `json:"foursquare_id,omitempty"` } // MessageEntity @@ -234,10 +234,10 @@ type MessageEntity struct { Length int `json:"length"` //url Optional. For “text_link” only, url that will be opened after user taps on the text - Url string `json:"url",omitempty` + Url string `json:"url,omitempty"` //user Optional. For “text_mention” only, the mentioned user - User User `json:"user",omitempty` + User User `json:"user,omitempty"` } // ChatMember , From 6360f1f7d9464545d98f2b478cd83240a8ced98e Mon Sep 17 00:00:00 2001 From: Ian Byrd Date: Thu, 10 Nov 2016 22:04:50 +0200 Subject: [PATCH 3/3] Refactoring, poor BC, closes #44 and probably resolves #41. - EntityType and ChatType enums introduced. - Documentation fixes, struct refactoring. - Poor BC, poor BC... --- bot.go | 4 ++-- options.go | 10 -------- telebot.go | 56 ++++++++++++++++++++++++++++++++++++------- types.go | 70 +++++++++++++++++++++++++++++------------------------- 4 files changed, 86 insertions(+), 54 deletions(-) diff --git a/bot.go b/bot.go index 0f8603c..2a795bc 100644 --- a/bot.go +++ b/bot.go @@ -502,10 +502,10 @@ func (b *Bot) SendVenue(recipient Recipient, venue *Venue, options *SendOptions) // // Currently, Telegram supports only a narrow range of possible // actions, these are aligned as constants of this package. -func (b *Bot) SendChatAction(recipient Recipient, action string) error { +func (b *Bot) SendChatAction(recipient Recipient, action ChatAction) error { params := map[string]string{ "chat_id": recipient.Destination(), - "action": action, + "action": string(action), } responseJSON, err := sendCommand("sendChatAction", b.Token, params) diff --git a/options.go b/options.go index cae87ea..38d0b4b 100644 --- a/options.go +++ b/options.go @@ -1,15 +1,5 @@ package telebot -// ParseMode determines the way client applications treat the text of the message -type ParseMode string - -// Supported ParseMode -const ( - ModeDefault ParseMode = "" - ModeMarkdown ParseMode = "Markdown" - ModeHTML ParseMode = "HTML" -) - // SendOptions represents a set of custom options that could // be appled to messages sent. type SendOptions struct { diff --git a/telebot.go b/telebot.go index 8cff222..6f91a69 100644 --- a/telebot.go +++ b/telebot.go @@ -27,14 +27,52 @@ // package telebot -// A bunch of available chat actions. +// ChatAction is a client-side status indicating bot activity. +type ChatAction string + +const ( + Typing ChatAction = "typing" + UploadingPhoto ChatAction = "upload_photo" + UploadingVideo ChatAction = "upload_video" + UploadingAudio ChatAction = "upload_audio" + UploadingDocument ChatAction = "upload_document" + RecordingVideo ChatAction = "record_video" + RecordingAudio ChatAction = "record_audio" + FindingLocation ChatAction = "find_location" +) + +// ParseMode determines the way client applications treat the text of the message +type ParseMode string + +const ( + ModeDefault ParseMode = "" + ModeMarkdown ParseMode = "Markdown" + ModeHTML ParseMode = "HTML" +) + +// EntityType is a MessageEntity type. +type EntityType string + +const ( + EntityMention EntityType = "mention" + EntityTMention EntityType = "text_mention" + EntityHashtag EntityType = "hashtag" + EntityCommand EntityType = "bot_command" + EntityURL EntityType = "url" + EntityEmail EntityType = "email" + EntityBold EntityType = "bold" + EntityItalic EntityType = "italic" + EntityCode EntityType = "code" + EntityCodeBlock EntityType = "pre" + EntityTextLink EntityType = "text_link" +) + +// ChatType represents one of the possible chat types. +type ChatType string + const ( - Typing = "typing" - UploadingPhoto = "upload_photo" - UploadingVideo = "upload_video" - UploadingAudio = "upload_audio" - UploadingDocument = "upload_document" - RecordingVideo = "record_video" - RecordingAudio = "record_audio" - FindingLocation = "find_location" + ChatPrivate ChatType = "private" + ChatGroup ChatType = "group" + ChatSuperGroup ChatType = "supergroup" + ChatChannel ChatType = "channel" ) diff --git a/types.go b/types.go index cdff896..f7327c3 100644 --- a/types.go +++ b/types.go @@ -26,13 +26,17 @@ func (u User) Destination() string { } // Chat object represents a Telegram user, bot or group chat. -// Title for channels and group chats +// // Type of chat, can be either “private”, “group”, "supergroup" or “channel” type Chat struct { - ID int64 `json:"id"` - Type string `json:"type"` + ID int64 `json:"id"` + + // See telebot.ChatType and consts. + Type ChatType `json:"type"` + + // Won't be there for ChatPrivate. + Title string `json:"title"` - Title string `json:"title"` FirstName string `json:"first_name"` LastName string `json:"last_name"` Username string `json:"username"` @@ -147,10 +151,11 @@ type KeyboardButton struct { InlineQuery string `json:"switch_inline_query,omitempty"` } -// InlineKeyboardMarkup represents an inline keyboard that appears right next -// to the message it belongs to. +// InlineKeyboardMarkup represents an inline keyboard that appears +// right next to the message it belongs to. type InlineKeyboardMarkup struct { - // Array of button rows, each represented by an Array of KeyboardButton objects. + // Array of button rows, each represented by + // an Array of KeyboardButton objects. InlineKeyboard [][]KeyboardButton `json:"inline_keyboard,omitempty"` } @@ -183,10 +188,14 @@ type Callback struct { // MessageID will be set if the button was attached to a message // sent via the bot in inline mode. MessageID string `json:"inline_message_id"` - Data string `json:"data"` + + // Data associated with the callback button. Be aware that + // a bad client can send arbitrary data in this field. + Data string `json:"data"` } -// CallbackResponse builds a response to an Callback query. +// CallbackResponse builds a response to a Callback query. +// // See also: https://core.telegram.org/bots/api#answerCallbackQuery type CallbackResponse struct { // The ID of the callback to which this is a response. @@ -208,7 +217,8 @@ type CallbackResponse struct { URL string `json:"url,omitempty"` } -// Venue object represents a venue location with name, address and optional foursquare id. +// Venue object represents a venue location with name, address and +// optional foursquare ID. type Venue struct { Location Location `json:"location"` Title string `json:"title"` @@ -216,44 +226,38 @@ type Venue struct { FoursquareID string `json:"foursquare_id,omitempty"` } -// MessageEntity -// This object represents one special entity in a text message. -// For example, hashtags, usernames, URLs, etc +// MessageEntity object represents "special" parts of text messages, +// including hashtags, usernames, URLs, etc. type MessageEntity struct { + // Specifies entity type. + Type EntityType `json:"type"` - // type Type of the entity. Can be mention (@username), hashtag, - // bot_command, url, email, bold (bold text), italic (italic text), - // code (monowidth string), pre (monowidth block), text_link (for clickable text URLs), - // text_mention (for users without usernames) - Type string `json:"type"` - - // offset Offset in UTF-16 code units to the start of the entity + // Offset in UTF-16 code units to the start of the entity. Offset int `json:"offset"` - //length Length of the entity in UTF-16 code units + // Length of the entity in UTF-16 code units. Length int `json:"length"` - //url Optional. For “text_link” only, url that will be opened after user taps on the text - Url string `json:"url,omitempty"` + // (Optional) For EntityTextLink entity type only. + // + // URL will be opened after user taps on the text. + URL string `json:"url,omitempty"` - //user Optional. For “text_mention” only, the mentioned user + // (Optional) For EntityTMention entity type only. User User `json:"user,omitempty"` } -// ChatMember , -// This struct contains information about one member of the chat. +// ChatMember object represents information about a single chat member. type ChatMember struct { User User `json:"user"` Status string `json:"status"` } -// UserProfilePhotos , -// This struct represent a user's profile pictures. -// -// Count : Total number of profile pictures the target user has -// -// Photos : Array of Array of PhotoSize , Requested profile pictures (in up to 4 sizes each) +// UserProfilePhotos object represent a user's profile pictures. type UserProfilePhotos struct { - Count int `json:"total_count"` + // Total number of profile pictures the target user has. + Count int `json:"total_count"` + + // Requested profile pictures (in up to 4 sizes each). Photos [][]Photo `json:"photos"` }