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