diff --git a/README.md b/README.md index 70f7634..c4865f7 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ import ( ) func main() { - bot, err := telebot.Create("SECRET TOKEN") + bot, err := telebot.NewBot("SECRET TOKEN") if err != nil { return } diff --git a/api.go b/api.go index 348cac2..8701787 100644 --- a/api.go +++ b/api.go @@ -14,7 +14,7 @@ import ( "strconv" ) -func api_GET(method string, token string, params url.Values) ([]byte, error) { +func sendCommand(method string, token string, params url.Values) ([]byte, error) { url := fmt.Sprintf("https://api.telegram.org/bot%s/%s?%s", token, method, params.Encode()) @@ -32,7 +32,7 @@ func api_GET(method string, token string, params url.Values) ([]byte, error) { return json, nil } -func api_POST(method, token, name, path string, params url.Values) ([]byte, error) { +func sendFile(method, token, name, path string, params url.Values) ([]byte, error) { file, err := os.Open(path) if err != nil { return []byte{}, err @@ -82,8 +82,8 @@ func api_POST(method, token, name, path string, params url.Values) ([]byte, erro return json, nil } -func api_getMe(token string) (User, error) { - me_json, err := api_GET("getMe", token, url.Values{}) +func getMe(token string) (User, error) { + me_json, err := sendCommand("getMe", token, url.Values{}) if err != nil { return User{}, err } @@ -106,10 +106,10 @@ func api_getMe(token string) (User, error) { } } -func api_getUpdates(token string, offset int, updates chan<- Update) error { +func getUpdates(token string, offset int, updates chan<- Update) error { params := url.Values{} params.Set("offset", strconv.Itoa(offset)) - updates_json, err := api_GET("getUpdates", token, params) + updates_json, err := sendCommand("getUpdates", token, params) if err != nil { return err } @@ -135,284 +135,3 @@ func api_getUpdates(token string, offset int, updates chan<- Update) error { return nil } - -func api_sendMessage(token string, recipient User, text string) error { - params := url.Values{} - params.Set("chat_id", strconv.Itoa(recipient.Id)) - params.Set("text", text) - response_json, err := api_GET("sendMessage", token, params) - if err != nil { - return err - } - - var response_recieved struct { - Ok bool - Description string - } - - err = json.Unmarshal(response_json, &response_recieved) - if err != nil { - return err - } - - if !response_recieved.Ok { - return SendError{response_recieved.Description} - } - - return nil -} - -func api_forwardMessage(token string, recipient User, message Message) error { - params := url.Values{} - params.Set("chat_id", strconv.Itoa(recipient.Id)) - params.Set("from_chat_id", strconv.Itoa(message.Origin().Id)) - params.Set("message_id", strconv.Itoa(message.Id)) - - response_json, err := api_GET("forwardMessage", token, params) - if err != nil { - return err - } - - var response_recieved struct { - Ok bool - Description string - } - - err = json.Unmarshal(response_json, &response_recieved) - if err != nil { - return err - } - - if !response_recieved.Ok { - return SendError{response_recieved.Description} - } - - return nil -} - -func api_sendPhoto(token string, recipient User, photo *Photo) error { - params := url.Values{} - params.Set("chat_id", strconv.Itoa(recipient.Id)) - params.Set("caption", photo.Caption) - - var response_json []byte - var err error - - if photo.Exists() { - params.Set("photo", photo.FileId) - response_json, err = api_GET("sendPhoto", token, params) - } else { - response_json, err = api_POST("sendPhoto", token, "photo", - photo.filename, params) - } - - if err != nil { - return err - } - - var response_recieved struct { - Ok bool - Result Message - Description string - } - - err = json.Unmarshal(response_json, &response_recieved) - if err != nil { - return err - } - - if !response_recieved.Ok { - return SendError{response_recieved.Description} - } - - thumbnails := &response_recieved.Result.Photo - photo.File = (*thumbnails)[len(*thumbnails)-1].File - - return nil -} - -func api_sendAudio(token string, recipient User, audio *Audio) error { - params := url.Values{} - params.Set("chat_id", strconv.Itoa(recipient.Id)) - - var response_json []byte - var err error - - if audio.Exists() { - params.Set("audio", audio.FileId) - response_json, err = api_GET("sendAudio", token, params) - } else { - response_json, err = api_POST("sendAudio", token, "audio", - audio.filename, params) - } - - if err != nil { - return err - } - - var response_recieved struct { - Ok bool - Result Message - Description string - } - - err = json.Unmarshal(response_json, &response_recieved) - if err != nil { - return err - } - - if !response_recieved.Ok { - return SendError{response_recieved.Description} - } - - *audio = response_recieved.Result.Audio - - return nil -} - -func api_sendDocument(token string, recipient User, doc *Document) error { - params := url.Values{} - params.Set("chat_id", strconv.Itoa(recipient.Id)) - - var response_json []byte - var err error - - if doc.Exists() { - params.Set("document", doc.FileId) - response_json, err = api_GET("sendDocument", token, params) - } else { - response_json, err = api_POST("sendDocument", token, "document", - doc.filename, params) - } - - if err != nil { - return err - } - - var response_recieved struct { - Ok bool - Result Message - Description string - } - - err = json.Unmarshal(response_json, &response_recieved) - if err != nil { - return err - } - - if !response_recieved.Ok { - return SendError{response_recieved.Description} - } - - *doc = response_recieved.Result.Document - - return nil -} - -func api_sendSticker(token string, recipient User, sticker *Sticker) error { - params := url.Values{} - params.Set("chat_id", strconv.Itoa(recipient.Id)) - - var response_json []byte - var err error - - if sticker.Exists() { - params.Set("sticker", sticker.FileId) - response_json, err = api_GET("sendSticker", token, params) - } else { - response_json, err = api_POST("sendSticker", token, "sticker", - sticker.filename, params) - } - - if err != nil { - return err - } - - var response_recieved struct { - Ok bool - Result Message - Description string - } - - err = json.Unmarshal(response_json, &response_recieved) - if err != nil { - return err - } - - if !response_recieved.Ok { - return SendError{response_recieved.Description} - } - - *sticker = response_recieved.Result.Sticker - - return nil -} - -func api_sendVideo(token string, recipient User, video *Video) error { - params := url.Values{} - params.Set("chat_id", strconv.Itoa(recipient.Id)) - - var response_json []byte - var err error - - if video.Exists() { - params.Set("video", video.FileId) - response_json, err = api_GET("sendVideo", token, params) - } else { - response_json, err = api_POST("sendVideo", token, "video", - video.filename, params) - } - - if err != nil { - return err - } - - var response_recieved struct { - Ok bool - Result Message - Description string - } - - err = json.Unmarshal(response_json, &response_recieved) - if err != nil { - return err - } - - if !response_recieved.Ok { - return SendError{response_recieved.Description} - } - - *video = response_recieved.Result.Video - - return nil -} - -func api_sendLocation(token string, recipient User, geo *Location) error { - params := url.Values{} - params.Set("chat_id", strconv.Itoa(recipient.Id)) - params.Set("latitude", fmt.Sprintf("%f", geo.Latitude)) - params.Set("longitude", fmt.Sprintf("%f", geo.Longitude)) - - response_json, err := api_GET("sendLocation", token, params) - - if err != nil { - return err - } - - var response_recieved struct { - Ok bool - Result Message - Description string - } - - err = json.Unmarshal(response_json, &response_recieved) - if err != nil { - return err - } - - if !response_recieved.Ok { - return SendError{response_recieved.Description} - } - - return nil -} diff --git a/bot.go b/bot.go index 6f3d2db..e27b48b 100644 --- a/bot.go +++ b/bot.go @@ -1,6 +1,10 @@ package telebot import ( + "encoding/json" + "fmt" + "net/url" + "strconv" "time" ) @@ -12,16 +16,30 @@ type Bot struct { Identity User } +// NewBot does try to build a Bot with token `token`, which +// is a secret API key assigned to particular bot. +func NewBot(token string) (*Bot, error) { + user, err := getMe(token) + if err != nil { + return nil, err + } + + return &Bot{ + Token: token, + Identity: user, + }, nil +} + // Listen periodically looks for updates and delivers new messages // to subscription channel. -func (b *Bot) Listen(subscription chan<- Message, interval time.Duration) { +func (b Bot) Listen(subscription chan<- Message, interval time.Duration) { updates := make(chan Update) pulse := time.NewTicker(interval) latest_update := 0 go func() { for range pulse.C { - go api_getUpdates(b.Token, + go getUpdates(b.Token, latest_update+1, updates) } @@ -39,13 +57,59 @@ func (b *Bot) Listen(subscription chan<- Message, interval time.Duration) { } // SendMessage sends a text message to recipient. -func (b *Bot) SendMessage(recipient User, message string) error { - return api_sendMessage(b.Token, recipient, message) +func (b Bot) SendMessage(recipient User, message string) error { + params := url.Values{} + params.Set("chat_id", strconv.Itoa(recipient.Id)) + params.Set("text", message) + response_json, err := sendCommand("sendMessage", b.Token, params) + if err != nil { + return err + } + + var response_recieved struct { + Ok bool + Description string + } + + err = json.Unmarshal(response_json, &response_recieved) + if err != nil { + return err + } + + if !response_recieved.Ok { + return SendError{response_recieved.Description} + } + + return nil } // ForwardMessage forwards a message to recipient. -func (b *Bot) ForwardMessage(recipient User, message Message) error { - return api_forwardMessage(b.Token, recipient, message) +func (b Bot) ForwardMessage(recipient User, message Message) error { + params := url.Values{} + params.Set("chat_id", strconv.Itoa(recipient.Id)) + params.Set("from_chat_id", strconv.Itoa(message.Origin().Id)) + params.Set("message_id", strconv.Itoa(message.Id)) + + response_json, err := sendCommand("forwardMessage", b.Token, params) + if err != nil { + return err + } + + var response_recieved struct { + Ok bool + Description string + } + + err = json.Unmarshal(response_json, &response_recieved) + if err != nil { + return err + } + + if !response_recieved.Ok { + return SendError{response_recieved.Description} + } + + return nil } // SendPhoto sends a photo object to recipient. @@ -54,8 +118,45 @@ func (b *Bot) ForwardMessage(recipient User, message Message) error { // the Telegram servers, so sending the same photo object // again, won't issue a new upload, but would make a use // of existing file on Telegram servers. -func (b *Bot) SendPhoto(recipient User, photo *Photo) error { - return api_sendPhoto(b.Token, recipient, photo) +func (b Bot) SendPhoto(recipient User, photo *Photo) error { + params := url.Values{} + params.Set("chat_id", strconv.Itoa(recipient.Id)) + params.Set("caption", photo.Caption) + + var response_json []byte + var err error + + if photo.Exists() { + params.Set("photo", photo.FileId) + response_json, err = sendCommand("sendPhoto", b.Token, params) + } else { + response_json, err = sendFile("sendPhoto", b.Token, "photo", + photo.filename, params) + } + + if err != nil { + return err + } + + var response_recieved struct { + Ok bool + Result Message + Description string + } + + err = json.Unmarshal(response_json, &response_recieved) + if err != nil { + return err + } + + if !response_recieved.Ok { + return SendError{response_recieved.Description} + } + + thumbnails := &response_recieved.Result.Photo + photo.File = (*thumbnails)[len(*thumbnails)-1].File + + return nil } // SendAudio sends an audio object to recipient. @@ -64,8 +165,43 @@ func (b *Bot) SendPhoto(recipient User, photo *Photo) error { // the Telegram servers, so sending the same audio object // again, won't issue a new upload, but would make a use // of existing file on Telegram servers. -func (b *Bot) SendAudio(recipient User, audio *Audio) error { - return api_sendAudio(b.Token, recipient, audio) +func (b Bot) SendAudio(recipient User, audio *Audio) error { + params := url.Values{} + params.Set("chat_id", strconv.Itoa(recipient.Id)) + + var response_json []byte + var err error + + if audio.Exists() { + params.Set("audio", audio.FileId) + response_json, err = sendCommand("sendAudio", b.Token, params) + } else { + response_json, err = sendFile("sendAudio", b.Token, "audio", + audio.filename, params) + } + + if err != nil { + return err + } + + var response_recieved struct { + Ok bool + Result Message + Description string + } + + err = json.Unmarshal(response_json, &response_recieved) + if err != nil { + return err + } + + if !response_recieved.Ok { + return SendError{response_recieved.Description} + } + + *audio = response_recieved.Result.Audio + + return nil } // SendDocument sends a general document object to recipient. @@ -74,8 +210,43 @@ func (b *Bot) SendAudio(recipient User, audio *Audio) error { // the Telegram servers, so sending the same document object // again, won't issue a new upload, but would make a use // of existing file on Telegram servers. -func (b *Bot) SendDocument(recipient User, doc *Document) error { - return api_sendDocument(b.Token, recipient, doc) +func (b Bot) SendDocument(recipient User, doc *Document) error { + params := url.Values{} + params.Set("chat_id", strconv.Itoa(recipient.Id)) + + var response_json []byte + var err error + + if doc.Exists() { + params.Set("document", doc.FileId) + response_json, err = sendCommand("sendDocument", b.Token, params) + } else { + response_json, err = sendFile("sendDocument", b.Token, "document", + doc.filename, params) + } + + if err != nil { + return err + } + + var response_recieved struct { + Ok bool + Result Message + Description string + } + + err = json.Unmarshal(response_json, &response_recieved) + if err != nil { + return err + } + + if !response_recieved.Ok { + return SendError{response_recieved.Description} + } + + *doc = response_recieved.Result.Document + + return nil } // SendSticker sends a general document object to recipient. @@ -85,7 +256,42 @@ func (b *Bot) SendDocument(recipient User, doc *Document) error { // again, won't issue a new upload, but would make a use // of existing file on Telegram servers. func (b *Bot) SendSticker(recipient User, sticker *Sticker) error { - return api_sendSticker(b.Token, recipient, sticker) + params := url.Values{} + params.Set("chat_id", strconv.Itoa(recipient.Id)) + + var response_json []byte + var err error + + if sticker.Exists() { + params.Set("sticker", sticker.FileId) + response_json, err = sendCommand("sendSticker", b.Token, params) + } else { + response_json, err = sendFile("sendSticker", b.Token, "sticker", + sticker.filename, params) + } + + if err != nil { + return err + } + + var response_recieved struct { + Ok bool + Result Message + Description string + } + + err = json.Unmarshal(response_json, &response_recieved) + if err != nil { + return err + } + + if !response_recieved.Ok { + return SendError{response_recieved.Description} + } + + *sticker = response_recieved.Result.Sticker + + return nil } // SendVideo sends a general document object to recipient. @@ -94,8 +300,43 @@ func (b *Bot) SendSticker(recipient User, sticker *Sticker) error { // the Telegram servers, so sending the same video object // again, won't issue a new upload, but would make a use // of existing file on Telegram servers. -func (b *Bot) SendVideo(recipient User, video *Video) error { - return api_sendVideo(b.Token, recipient, video) +func (b Bot) SendVideo(recipient User, video *Video) error { + params := url.Values{} + params.Set("chat_id", strconv.Itoa(recipient.Id)) + + var response_json []byte + var err error + + if video.Exists() { + params.Set("video", video.FileId) + response_json, err = sendCommand("sendVideo", b.Token, params) + } else { + response_json, err = sendFile("sendVideo", b.Token, "video", + video.filename, params) + } + + if err != nil { + return err + } + + var response_recieved struct { + Ok bool + Result Message + Description string + } + + err = json.Unmarshal(response_json, &response_recieved) + if err != nil { + return err + } + + if !response_recieved.Ok { + return SendError{response_recieved.Description} + } + + *video = response_recieved.Result.Video + + return nil } // SendLocation sends a general document object to recipient. @@ -104,6 +345,32 @@ func (b *Bot) SendVideo(recipient User, video *Video) error { // the Telegram servers, so sending the same video object // again, won't issue a new upload, but would make a use // of existing file on Telegram servers. -func (b *Bot) SendLocation(recipient User, geo *Location) error { - return api_sendLocation(b.Token, recipient, geo) +func (b Bot) SendLocation(recipient User, geo *Location) error { + params := url.Values{} + params.Set("chat_id", strconv.Itoa(recipient.Id)) + params.Set("latitude", fmt.Sprintf("%f", geo.Latitude)) + params.Set("longitude", fmt.Sprintf("%f", geo.Longitude)) + + response_json, err := sendCommand("sendLocation", b.Token, params) + + if err != nil { + return err + } + + var response_recieved struct { + Ok bool + Result Message + Description string + } + + err = json.Unmarshal(response_json, &response_recieved) + if err != nil { + return err + } + + if !response_recieved.Ok { + return SendError{response_recieved.Description} + } + + return nil } diff --git a/errors.go b/errors.go index 2368a82..26dc63e 100644 --- a/errors.go +++ b/errors.go @@ -17,6 +17,7 @@ type SendError struct { Payload string } +// FileError occurs when local file can't be read. type FileError struct { Payload string } diff --git a/file.go b/file.go new file mode 100644 index 0000000..f1faee6 --- /dev/null +++ b/file.go @@ -0,0 +1,40 @@ +package telebot + +import ( + "fmt" + "os" +) + +// File object represents any sort of file. +type File struct { + FileId string `json:"file_id"` + FileSize int `json:"file_size"` + + // Local absolute path to file on file system. Valid only for + // new files, meant to be uploaded soon. + filename string +} + +// NewFile attempts to create a File object, leading to a real +// file on the file system, that could be uploaded later. +// +// Notice that NewFile doesn't upload file, but only creates +// a descriptor for it. +func NewFile(path string) (File, error) { + if _, err := os.Stat(path); os.IsNotExist(err) { + return File{}, FileError{ + fmt.Sprintf("'%s' does not exist!", path), + } + } + + return File{filename: path}, nil +} + +// Exists says whether file presents on Telegram servers or not. +func (f File) Exists() bool { + if f.filename == "" { + return true + } + + return false +} diff --git a/message.go b/message.go index bf3e476..ca89159 100644 --- a/message.go +++ b/message.go @@ -97,9 +97,9 @@ type Message struct { func (m Message) Origin() User { if (m.Chat != User{}) { return m.Chat - } else { - return m.Sender } + + return m.Sender } // Time returns the moment of message creation in local time. diff --git a/telebot.go b/telebot.go index eab8057..910426c 100644 --- a/telebot.go +++ b/telebot.go @@ -9,7 +9,7 @@ // ) // // func main() { -// bot, err := telebot.Create("SECRET_TOKEN") +// bot, err := telebot.NewBot("SECRET_TOKEN") // if err != nil { // return // } @@ -26,37 +26,3 @@ // } // package telebot - -import ( - "fmt" - "os" -) - -// Create does try to build a Bot with token `token`, which -// is a secret API key assigned to particular bot. -func Create(token string) (Bot, error) { - user, err := api_getMe(token) - if err != nil { - return Bot{}, err - } - - return Bot{ - Token: token, - Identity: user, - }, nil -} - -// NewFile attempts to create a File object, leading to a real -// file on the file system, that could be uploaded later. -// -// Notice that NewFile doesn't upload file, but only creates -// a descriptor for it. -func NewFile(path string) (File, error) { - if _, err := os.Stat(path); os.IsNotExist(err) { - return File{}, FileError{ - fmt.Sprintf("'%s' does not exist!", path), - } - } - - return File{filename: path}, nil -} diff --git a/telebot_test.go b/telebot_test.go index e744c25..7adb06d 100644 --- a/telebot_test.go +++ b/telebot_test.go @@ -16,7 +16,7 @@ func TestTelebot(t *testing.T) { t.Fatal("Could't find TELEBOT_SECRET, aborting.") } - _, err := Create(token) + _, err := NewBot(token) if err != nil { t.Fatal(err) } diff --git a/types.go b/types.go index bf695fc..4c2edcd 100644 --- a/types.go +++ b/types.go @@ -17,25 +17,6 @@ type Update struct { Payload Message `json:"message"` } -// File object represents any sort of file. -type File struct { - FileId string `json:"file_id"` - FileSize int `json:"file_size"` - - // Local absolute path to file on file system. Valid only for - // new files, meant to be uploaded soon. - filename string -} - -// Exists says whether file presents on Telegram servers or not. -func (f File) Exists() bool { - if f.filename == "" { - return true - } - - return false -} - // Thumbnail object represents a image/sticker of particular size. type Thumbnail struct { File @@ -88,7 +69,7 @@ type Sticker struct { Preview Thumbnail `json:"thumb"` } -// Video object represents +// Video object represents an MP4-encoded video. type Video struct { Audio @@ -102,6 +83,7 @@ type Video struct { Preview Thumbnail `json:"thumb"` } +// Contact object represents a contact to Telegram user type Contact struct { PhoneNumber string `json:"phone_number"` FirstName string `json:"first_name"` @@ -111,6 +93,7 @@ type Contact struct { Username string `json:"user_id"` } +// Location object represents geographic position. type Location struct { Longitude float32 `json:"longitude"` Latitude float32 `json:"latitude"`