methods.go (view raw)
1package tgbotapi
2
3import (
4 "bytes"
5 "encoding/json"
6 "errors"
7 "fmt"
8 "io"
9 "io/ioutil"
10 "log"
11 "mime/multipart"
12 "net/http"
13 "net/url"
14 "os"
15 "strconv"
16)
17
18// Telegram constants
19const (
20 // APIEndpoint is the endpoint for all API methods, with formatting for Sprintf
21 APIEndpoint = "https://api.telegram.org/bot%s/%s"
22)
23
24// Constant values for ChatActions
25const (
26 ChatTyping = "typing"
27 ChatUploadPhoto = "upload_photo"
28 ChatRecordVideo = "record_video"
29 ChatUploadVideo = "upload_video"
30 ChatRecordAudio = "record_audio"
31 ChatUploadAudio = "upload_audio"
32 ChatUploadDocument = "upload_document"
33 ChatFindLocation = "find_location"
34)
35
36// API errors
37const (
38 // APIForbidden happens when a token is bad
39 APIForbidden = "forbidden"
40)
41
42// MessageConfig contains information about a SendMessage request.
43type MessageConfig struct {
44 ChatID int
45 Text string
46 DisableWebPagePreview bool
47 ReplyToMessageID int
48 ReplyMarkup interface{}
49}
50
51// ForwardConfig contains information about a ForwardMessage request.
52type ForwardConfig struct {
53 ChatID int
54 FromChatID int
55 MessageID int
56}
57
58// PhotoConfig contains information about a SendPhoto request.
59type PhotoConfig struct {
60 ChatID int
61 Caption string
62 ReplyToMessageID int
63 ReplyMarkup interface{}
64 UseExistingPhoto bool
65 FilePath string
66 FileID string
67}
68
69// AudioConfig contains information about a SendAudio request.
70type AudioConfig struct {
71 ChatID int
72 ReplyToMessageID int
73 ReplyMarkup interface{}
74 UseExistingAudio bool
75 FilePath string
76 FileID string
77}
78
79// DocumentConfig contains information about a SendDocument request.
80type DocumentConfig struct {
81 ChatID int
82 ReplyToMessageID int
83 ReplyMarkup interface{}
84 UseExistingDocument bool
85 FilePath string
86 FileID string
87}
88
89// StickerConfig contains information about a SendSticker request.
90type StickerConfig struct {
91 ChatID int
92 ReplyToMessageID int
93 ReplyMarkup interface{}
94 UseExistingSticker bool
95 FilePath string
96 FileID string
97}
98
99// VideoConfig contains information about a SendVideo request.
100type VideoConfig struct {
101 ChatID int
102 Duration int
103 Caption string
104 ReplyToMessageID int
105 ReplyMarkup interface{}
106 UseExistingVideo bool
107 FilePath string
108 FileID string
109}
110
111// LocationConfig contains information about a SendLocation request.
112type LocationConfig struct {
113 ChatID int
114 Latitude float64
115 Longitude float64
116 ReplyToMessageID int
117 ReplyMarkup interface{}
118}
119
120// ChatActionConfig contains information about a SendChatAction request.
121type ChatActionConfig struct {
122 ChatID int
123 Action string
124}
125
126// UserProfilePhotosConfig contains information about a GetUserProfilePhotos request.
127type UserProfilePhotosConfig struct {
128 UserID int
129 Offset int
130 Limit int
131}
132
133// UpdateConfig contains information about a GetUpdates request.
134type UpdateConfig struct {
135 Offset int
136 Limit int
137 Timeout int
138}
139
140// WebhookConfig contains information about a SetWebhook request.
141type WebhookConfig struct {
142 Clear bool
143 URL *url.URL
144}
145
146// MakeRequest makes a request to a specific endpoint with our token.
147// All requests are POSTs because Telegram doesn't care, and it's easier.
148func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse, error) {
149 resp, err := bot.Client.PostForm(fmt.Sprintf(APIEndpoint, bot.Token, endpoint), params)
150 if err != nil {
151 return APIResponse{}, err
152 }
153 defer resp.Body.Close()
154
155 if resp.StatusCode == http.StatusForbidden {
156 return APIResponse{}, errors.New(APIForbidden)
157 }
158
159 bytes, err := ioutil.ReadAll(resp.Body)
160 if err != nil {
161 return APIResponse{}, err
162 }
163
164 if bot.Debug {
165 log.Println(endpoint, string(bytes))
166 }
167
168 var apiResp APIResponse
169 json.Unmarshal(bytes, &apiResp)
170
171 if !apiResp.Ok {
172 return APIResponse{}, errors.New(apiResp.Description)
173 }
174
175 return apiResp, nil
176}
177
178// UploadFile makes a request to the API with a file.
179//
180// Requires the parameter to hold the file not be in the params.
181func (bot *BotAPI) UploadFile(endpoint string, params map[string]string, fieldname string, filename string) (APIResponse, error) {
182 var b bytes.Buffer
183 w := multipart.NewWriter(&b)
184
185 f, err := os.Open(filename)
186 if err != nil {
187 return APIResponse{}, err
188 }
189
190 fw, err := w.CreateFormFile(fieldname, filename)
191 if err != nil {
192 return APIResponse{}, err
193 }
194
195 if _, err = io.Copy(fw, f); err != nil {
196 return APIResponse{}, err
197 }
198
199 for key, val := range params {
200 if fw, err = w.CreateFormField(key); err != nil {
201 return APIResponse{}, err
202 }
203
204 if _, err = fw.Write([]byte(val)); err != nil {
205 return APIResponse{}, err
206 }
207 }
208
209 w.Close()
210
211 req, err := http.NewRequest("POST", fmt.Sprintf(APIEndpoint, bot.Token, endpoint), &b)
212 if err != nil {
213 return APIResponse{}, err
214 }
215
216 req.Header.Set("Content-Type", w.FormDataContentType())
217
218 res, err := bot.Client.Do(req)
219 if err != nil {
220 return APIResponse{}, err
221 }
222
223 bytes, err := ioutil.ReadAll(res.Body)
224 if err != nil {
225 return APIResponse{}, err
226 }
227
228 if bot.Debug {
229 log.Println(string(bytes[:]))
230 }
231
232 var apiResp APIResponse
233 json.Unmarshal(bytes, &apiResp)
234
235 return apiResp, nil
236}
237
238// GetMe fetches the currently authenticated bot.
239//
240// There are no parameters for this method.
241func (bot *BotAPI) GetMe() (User, error) {
242 resp, err := bot.MakeRequest("getMe", nil)
243 if err != nil {
244 return User{}, err
245 }
246
247 var user User
248 json.Unmarshal(resp.Result, &user)
249
250 if bot.Debug {
251 log.Printf("getMe: %+v\n", user)
252 }
253
254 return user, nil
255}
256
257// SendMessage sends a Message to a chat.
258//
259// Requires ChatID and Text.
260// DisableWebPagePreview, ReplyToMessageID, and ReplyMarkup are optional.
261func (bot *BotAPI) SendMessage(config MessageConfig) (Message, error) {
262 v := url.Values{}
263 v.Add("chat_id", strconv.Itoa(config.ChatID))
264 v.Add("text", config.Text)
265 v.Add("disable_web_page_preview", strconv.FormatBool(config.DisableWebPagePreview))
266 if config.ReplyToMessageID != 0 {
267 v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
268 }
269 if config.ReplyMarkup != nil {
270 data, err := json.Marshal(config.ReplyMarkup)
271 if err != nil {
272 return Message{}, err
273 }
274
275 v.Add("reply_markup", string(data))
276 }
277
278 resp, err := bot.MakeRequest("SendMessage", v)
279 if err != nil {
280 return Message{}, err
281 }
282
283 var message Message
284 json.Unmarshal(resp.Result, &message)
285
286 if bot.Debug {
287 log.Printf("SendMessage req : %+v\n", v)
288 log.Printf("SendMessage resp: %+v\n", message)
289 }
290
291 return message, nil
292}
293
294// ForwardMessage forwards a message from one chat to another.
295//
296// Requires ChatID (destination), FromChatID (source), and MessageID.
297func (bot *BotAPI) ForwardMessage(config ForwardConfig) (Message, error) {
298 v := url.Values{}
299 v.Add("chat_id", strconv.Itoa(config.ChatID))
300 v.Add("from_chat_id", strconv.Itoa(config.FromChatID))
301 v.Add("message_id", strconv.Itoa(config.MessageID))
302
303 resp, err := bot.MakeRequest("forwardMessage", v)
304 if err != nil {
305 return Message{}, err
306 }
307
308 var message Message
309 json.Unmarshal(resp.Result, &message)
310
311 if bot.Debug {
312 log.Printf("forwardMessage req : %+v\n", v)
313 log.Printf("forwardMessage resp: %+v\n", message)
314 }
315
316 return message, nil
317}
318
319// SendPhoto sends or uploads a photo to a chat.
320//
321// Requires ChatID and FileID OR FilePath.
322// Caption, ReplyToMessageID, and ReplyMarkup are optional.
323func (bot *BotAPI) SendPhoto(config PhotoConfig) (Message, error) {
324 if config.UseExistingPhoto {
325 v := url.Values{}
326 v.Add("chat_id", strconv.Itoa(config.ChatID))
327 v.Add("photo", config.FileID)
328 if config.Caption != "" {
329 v.Add("caption", config.Caption)
330 }
331 if config.ReplyToMessageID != 0 {
332 v.Add("reply_to_message_id", strconv.Itoa(config.ChatID))
333 }
334 if config.ReplyMarkup != nil {
335 data, err := json.Marshal(config.ReplyMarkup)
336 if err != nil {
337 return Message{}, err
338 }
339
340 v.Add("reply_markup", string(data))
341 }
342
343 resp, err := bot.MakeRequest("SendPhoto", v)
344 if err != nil {
345 return Message{}, err
346 }
347
348 var message Message
349 json.Unmarshal(resp.Result, &message)
350
351 if bot.Debug {
352 log.Printf("SendPhoto req : %+v\n", v)
353 log.Printf("SendPhoto resp: %+v\n", message)
354 }
355
356 return message, nil
357 }
358
359 params := make(map[string]string)
360 params["chat_id"] = strconv.Itoa(config.ChatID)
361 if config.Caption != "" {
362 params["caption"] = config.Caption
363 }
364 if config.ReplyToMessageID != 0 {
365 params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
366 }
367 if config.ReplyMarkup != nil {
368 data, err := json.Marshal(config.ReplyMarkup)
369 if err != nil {
370 return Message{}, err
371 }
372
373 params["reply_markup"] = string(data)
374 }
375
376 resp, err := bot.UploadFile("SendPhoto", params, "photo", config.FilePath)
377 if err != nil {
378 return Message{}, err
379 }
380
381 var message Message
382 json.Unmarshal(resp.Result, &message)
383
384 if bot.Debug {
385 log.Printf("SendPhoto resp: %+v\n", message)
386 }
387
388 return message, nil
389}
390
391// SendAudio sends or uploads an audio clip to a chat.
392// If using a file, the file must be encoded as an .ogg with OPUS.
393//
394// Requires ChatID and FileID OR FilePath.
395// ReplyToMessageID and ReplyMarkup are optional.
396func (bot *BotAPI) SendAudio(config AudioConfig) (Message, error) {
397 if config.UseExistingAudio {
398 v := url.Values{}
399 v.Add("chat_id", strconv.Itoa(config.ChatID))
400 v.Add("audio", config.FileID)
401 if config.ReplyToMessageID != 0 {
402 v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
403 }
404 if config.ReplyMarkup != nil {
405 data, err := json.Marshal(config.ReplyMarkup)
406 if err != nil {
407 return Message{}, err
408 }
409
410 v.Add("reply_markup", string(data))
411 }
412
413 resp, err := bot.MakeRequest("sendAudio", v)
414 if err != nil {
415 return Message{}, err
416 }
417
418 var message Message
419 json.Unmarshal(resp.Result, &message)
420
421 if bot.Debug {
422 log.Printf("sendAudio req : %+v\n", v)
423 log.Printf("sendAudio resp: %+v\n", message)
424 }
425
426 return message, nil
427 }
428
429 params := make(map[string]string)
430
431 params["chat_id"] = strconv.Itoa(config.ChatID)
432 if config.ReplyToMessageID != 0 {
433 params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
434 }
435 if config.ReplyMarkup != nil {
436 data, err := json.Marshal(config.ReplyMarkup)
437 if err != nil {
438 return Message{}, err
439 }
440
441 params["reply_markup"] = string(data)
442 }
443
444 resp, err := bot.UploadFile("sendAudio", params, "audio", config.FilePath)
445 if err != nil {
446 return Message{}, err
447 }
448
449 var message Message
450 json.Unmarshal(resp.Result, &message)
451
452 if bot.Debug {
453 log.Printf("sendAudio resp: %+v\n", message)
454 }
455
456 return message, nil
457}
458
459// SendDocument sends or uploads a document to a chat.
460//
461// Requires ChatID and FileID OR FilePath.
462// ReplyToMessageID and ReplyMarkup are optional.
463func (bot *BotAPI) SendDocument(config DocumentConfig) (Message, error) {
464 if config.UseExistingDocument {
465 v := url.Values{}
466 v.Add("chat_id", strconv.Itoa(config.ChatID))
467 v.Add("document", config.FileID)
468 if config.ReplyToMessageID != 0 {
469 v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
470 }
471 if config.ReplyMarkup != nil {
472 data, err := json.Marshal(config.ReplyMarkup)
473 if err != nil {
474 return Message{}, err
475 }
476
477 v.Add("reply_markup", string(data))
478 }
479
480 resp, err := bot.MakeRequest("sendDocument", v)
481 if err != nil {
482 return Message{}, err
483 }
484
485 var message Message
486 json.Unmarshal(resp.Result, &message)
487
488 if bot.Debug {
489 log.Printf("sendDocument req : %+v\n", v)
490 log.Printf("sendDocument resp: %+v\n", message)
491 }
492
493 return message, nil
494 }
495
496 params := make(map[string]string)
497
498 params["chat_id"] = strconv.Itoa(config.ChatID)
499 if config.ReplyToMessageID != 0 {
500 params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
501 }
502 if config.ReplyMarkup != nil {
503 data, err := json.Marshal(config.ReplyMarkup)
504 if err != nil {
505 return Message{}, err
506 }
507
508 params["reply_markup"] = string(data)
509 }
510
511 resp, err := bot.UploadFile("sendDocument", params, "document", config.FilePath)
512 if err != nil {
513 return Message{}, err
514 }
515
516 var message Message
517 json.Unmarshal(resp.Result, &message)
518
519 if bot.Debug {
520 log.Printf("sendDocument resp: %+v\n", message)
521 }
522
523 return message, nil
524}
525
526// SendSticker sends or uploads a sticker to a chat.
527//
528// Requires ChatID and FileID OR FilePath.
529// ReplyToMessageID and ReplyMarkup are optional.
530func (bot *BotAPI) SendSticker(config StickerConfig) (Message, error) {
531 if config.UseExistingSticker {
532 v := url.Values{}
533 v.Add("chat_id", strconv.Itoa(config.ChatID))
534 v.Add("sticker", config.FileID)
535 if config.ReplyToMessageID != 0 {
536 v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
537 }
538 if config.ReplyMarkup != nil {
539 data, err := json.Marshal(config.ReplyMarkup)
540 if err != nil {
541 return Message{}, err
542 }
543
544 v.Add("reply_markup", string(data))
545 }
546
547 resp, err := bot.MakeRequest("sendSticker", v)
548 if err != nil {
549 return Message{}, err
550 }
551
552 var message Message
553 json.Unmarshal(resp.Result, &message)
554
555 if bot.Debug {
556 log.Printf("sendSticker req : %+v\n", v)
557 log.Printf("sendSticker resp: %+v\n", message)
558 }
559
560 return message, nil
561 }
562
563 params := make(map[string]string)
564
565 params["chat_id"] = strconv.Itoa(config.ChatID)
566 if config.ReplyToMessageID != 0 {
567 params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
568 }
569 if config.ReplyMarkup != nil {
570 data, err := json.Marshal(config.ReplyMarkup)
571 if err != nil {
572 return Message{}, err
573 }
574
575 params["reply_markup"] = string(data)
576 }
577
578 resp, err := bot.UploadFile("sendSticker", params, "sticker", config.FilePath)
579 if err != nil {
580 return Message{}, err
581 }
582
583 var message Message
584 json.Unmarshal(resp.Result, &message)
585
586 if bot.Debug {
587 log.Printf("sendSticker resp: %+v\n", message)
588 }
589
590 return message, nil
591}
592
593// SendVideo sends or uploads a video to a chat.
594//
595// Requires ChatID and FileID OR FilePath.
596// ReplyToMessageID and ReplyMarkup are optional.
597func (bot *BotAPI) SendVideo(config VideoConfig) (Message, error) {
598 if config.UseExistingVideo {
599 v := url.Values{}
600 v.Add("chat_id", strconv.Itoa(config.ChatID))
601 v.Add("video", config.FileID)
602 if config.ReplyToMessageID != 0 {
603 v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
604 }
605 if config.Duration != 0 {
606 v.Add("duration", strconv.Itoa(config.Duration))
607 }
608 if config.Caption != "" {
609 v.Add("caption", config.Caption)
610 }
611 if config.ReplyMarkup != nil {
612 data, err := json.Marshal(config.ReplyMarkup)
613 if err != nil {
614 return Message{}, err
615 }
616
617 v.Add("reply_markup", string(data))
618 }
619
620 resp, err := bot.MakeRequest("sendVideo", v)
621 if err != nil {
622 return Message{}, err
623 }
624
625 var message Message
626 json.Unmarshal(resp.Result, &message)
627
628 if bot.Debug {
629 log.Printf("sendVideo req : %+v\n", v)
630 log.Printf("sendVideo resp: %+v\n", message)
631 }
632
633 return message, nil
634 }
635
636 params := make(map[string]string)
637
638 params["chat_id"] = strconv.Itoa(config.ChatID)
639 if config.ReplyToMessageID != 0 {
640 params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
641 }
642 if config.ReplyMarkup != nil {
643 data, err := json.Marshal(config.ReplyMarkup)
644 if err != nil {
645 return Message{}, err
646 }
647
648 params["reply_markup"] = string(data)
649 }
650
651 resp, err := bot.UploadFile("sendVideo", params, "video", config.FilePath)
652 if err != nil {
653 return Message{}, err
654 }
655
656 var message Message
657 json.Unmarshal(resp.Result, &message)
658
659 if bot.Debug {
660 log.Printf("sendVideo resp: %+v\n", message)
661 }
662
663 return message, nil
664}
665
666// SendLocation sends a location to a chat.
667//
668// Requires ChatID, Latitude, and Longitude.
669// ReplyToMessageID and ReplyMarkup are optional.
670func (bot *BotAPI) SendLocation(config LocationConfig) (Message, error) {
671 v := url.Values{}
672 v.Add("chat_id", strconv.Itoa(config.ChatID))
673 v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64))
674 v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64))
675 if config.ReplyToMessageID != 0 {
676 v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
677 }
678 if config.ReplyMarkup != nil {
679 data, err := json.Marshal(config.ReplyMarkup)
680 if err != nil {
681 return Message{}, err
682 }
683
684 v.Add("reply_markup", string(data))
685 }
686
687 resp, err := bot.MakeRequest("sendLocation", v)
688 if err != nil {
689 return Message{}, err
690 }
691
692 var message Message
693 json.Unmarshal(resp.Result, &message)
694
695 if bot.Debug {
696 log.Printf("sendLocation req : %+v\n", v)
697 log.Printf("sendLocation resp: %+v\n", message)
698 }
699
700 return message, nil
701}
702
703// SendChatAction sets a current action in a chat.
704//
705// Requires ChatID and a valid Action (see Chat constants).
706func (bot *BotAPI) SendChatAction(config ChatActionConfig) error {
707 v := url.Values{}
708 v.Add("chat_id", strconv.Itoa(config.ChatID))
709 v.Add("action", config.Action)
710
711 _, err := bot.MakeRequest("sendChatAction", v)
712 if err != nil {
713 return err
714 }
715
716 return nil
717}
718
719// GetUserProfilePhotos gets a user's profile photos.
720//
721// Requires UserID.
722// Offset and Limit are optional.
723func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) {
724 v := url.Values{}
725 v.Add("user_id", strconv.Itoa(config.UserID))
726 if config.Offset != 0 {
727 v.Add("offset", strconv.Itoa(config.Offset))
728 }
729 if config.Limit != 0 {
730 v.Add("limit", strconv.Itoa(config.Limit))
731 }
732
733 resp, err := bot.MakeRequest("getUserProfilePhotos", v)
734 if err != nil {
735 return UserProfilePhotos{}, err
736 }
737
738 var profilePhotos UserProfilePhotos
739 json.Unmarshal(resp.Result, &profilePhotos)
740
741 if bot.Debug {
742 log.Printf("getUserProfilePhotos req : %+v\n", v)
743 log.Printf("getUserProfilePhotos resp: %+v\n", profilePhotos)
744 }
745
746 return profilePhotos, nil
747}
748
749// GetUpdates fetches updates.
750// If a WebHook is set, this will not return any data!
751//
752// Offset, Limit, and Timeout are optional.
753// To not get old items, set Offset to one higher than the previous item.
754// Set Timeout to a large number to reduce requests and get responses instantly.
755func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) {
756 v := url.Values{}
757 if config.Offset > 0 {
758 v.Add("offset", strconv.Itoa(config.Offset))
759 }
760 if config.Limit > 0 {
761 v.Add("limit", strconv.Itoa(config.Limit))
762 }
763 if config.Timeout > 0 {
764 v.Add("timeout", strconv.Itoa(config.Timeout))
765 }
766
767 resp, err := bot.MakeRequest("getUpdates", v)
768 if err != nil {
769 return []Update{}, err
770 }
771
772 var updates []Update
773 json.Unmarshal(resp.Result, &updates)
774
775 if bot.Debug {
776 log.Printf("getUpdates: %+v\n", updates)
777 }
778
779 return updates, nil
780}
781
782// SetWebhook sets a webhook.
783// If this is set, GetUpdates will not get any data!
784//
785// Requires Url OR to set Clear to true.
786func (bot *BotAPI) SetWebhook(config WebhookConfig) error {
787 v := url.Values{}
788 if !config.Clear {
789 v.Add("url", config.URL.String())
790 }
791
792 _, err := bot.MakeRequest("setWebhook", v)
793
794 return err
795}