methods.go (view raw)
1package tgbotapi
2
3import (
4 "bytes"
5 "encoding/json"
6 "errors"
7 "fmt"
8 "github.com/technoweenie/multipartstreamer"
9 "io"
10 "io/ioutil"
11 "log"
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// Constant values for ParseMode in MessageConfig
43const (
44 ModeMarkdown = "Markdown"
45)
46
47// MessageConfig contains information about a SendMessage request.
48type MessageConfig struct {
49 ChatID int
50 Text string
51 ParseMode string
52 DisableWebPagePreview bool
53 ReplyToMessageID int
54 ReplyMarkup interface{}
55}
56
57// ForwardConfig contains information about a ForwardMessage request.
58type ForwardConfig struct {
59 ChatID int
60 FromChatID int
61 MessageID int
62}
63
64// PhotoConfig contains information about a SendPhoto request.
65type PhotoConfig struct {
66 ChatID int
67 Caption string
68 ReplyToMessageID int
69 ReplyMarkup interface{}
70 UseExistingPhoto bool
71 FilePath string
72 File interface{}
73 FileID string
74}
75
76// AudioConfig contains information about a SendAudio request.
77type AudioConfig struct {
78 ChatID int
79 Duration int
80 Performer string
81 Title string
82 ReplyToMessageID int
83 ReplyMarkup interface{}
84 UseExistingAudio bool
85 FilePath string
86 File interface{}
87 FileID string
88}
89
90// DocumentConfig contains information about a SendDocument request.
91type DocumentConfig struct {
92 ChatID int
93 ReplyToMessageID int
94 ReplyMarkup interface{}
95 UseExistingDocument bool
96 FilePath string
97 File interface{}
98 FileID string
99}
100
101// StickerConfig contains information about a SendSticker request.
102type StickerConfig struct {
103 ChatID int
104 ReplyToMessageID int
105 ReplyMarkup interface{}
106 UseExistingSticker bool
107 FilePath string
108 File interface{}
109 FileID string
110}
111
112// VideoConfig contains information about a SendVideo request.
113type VideoConfig struct {
114 ChatID int
115 Duration int
116 Caption string
117 ReplyToMessageID int
118 ReplyMarkup interface{}
119 UseExistingVideo bool
120 FilePath string
121 File interface{}
122 FileID string
123}
124
125// VoiceConfig contains information about a SendVoice request.
126type VoiceConfig struct {
127 ChatID int
128 Duration int
129 ReplyToMessageID int
130 ReplyMarkup interface{}
131 UseExistingVoice bool
132 FilePath string
133 File interface{}
134 FileID string
135}
136
137// LocationConfig contains information about a SendLocation request.
138type LocationConfig struct {
139 ChatID int
140 Latitude float64
141 Longitude float64
142 ReplyToMessageID int
143 ReplyMarkup interface{}
144}
145
146// ChatActionConfig contains information about a SendChatAction request.
147type ChatActionConfig struct {
148 ChatID int
149 Action string
150}
151
152// UserProfilePhotosConfig contains information about a GetUserProfilePhotos request.
153type UserProfilePhotosConfig struct {
154 UserID int
155 Offset int
156 Limit int
157}
158
159// UpdateConfig contains information about a GetUpdates request.
160type UpdateConfig struct {
161 Offset int
162 Limit int
163 Timeout int
164}
165
166// WebhookConfig contains information about a SetWebhook request.
167type WebhookConfig struct {
168 Clear bool
169 URL *url.URL
170 Certificate interface{}
171}
172
173// FileBytes contains information about a set of bytes to upload as a File.
174type FileBytes struct {
175 Name string
176 Bytes []byte
177}
178
179// FileReader contains information about a reader to upload as a File.
180// If Size is -1, it will read the entire Reader into memory to calculate a Size.
181type FileReader struct {
182 Name string
183 Reader io.Reader
184 Size int64
185}
186
187// MakeRequest makes a request to a specific endpoint with our token.
188// All requests are POSTs because Telegram doesn't care, and it's easier.
189func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse, error) {
190 resp, err := bot.Client.PostForm(fmt.Sprintf(APIEndpoint, bot.Token, endpoint), params)
191 if err != nil {
192 return APIResponse{}, err
193 }
194 defer resp.Body.Close()
195
196 if resp.StatusCode == http.StatusForbidden {
197 return APIResponse{}, errors.New(APIForbidden)
198 }
199
200 bytes, err := ioutil.ReadAll(resp.Body)
201 if err != nil {
202 return APIResponse{}, err
203 }
204
205 if bot.Debug {
206 log.Println(endpoint, string(bytes))
207 }
208
209 var apiResp APIResponse
210 json.Unmarshal(bytes, &apiResp)
211
212 if !apiResp.Ok {
213 return APIResponse{}, errors.New(apiResp.Description)
214 }
215
216 return apiResp, nil
217}
218
219// UploadFile makes a request to the API with a file.
220//
221// Requires the parameter to hold the file not be in the params.
222// File should be a string to a file path, a FileBytes struct, or a FileReader struct.
223func (bot *BotAPI) UploadFile(endpoint string, params map[string]string, fieldname string, file interface{}) (APIResponse, error) {
224 ms := multipartstreamer.New()
225 ms.WriteFields(params)
226
227 switch f := file.(type) {
228 case string:
229 fileHandle, err := os.Open(f)
230 if err != nil {
231 return APIResponse{}, err
232 }
233 defer fileHandle.Close()
234
235 fi, err := os.Stat(f)
236 if err != nil {
237 return APIResponse{}, err
238 }
239
240 ms.WriteReader(fieldname, fileHandle.Name(), fi.Size(), fileHandle)
241 case FileBytes:
242 buf := bytes.NewBuffer(f.Bytes)
243 ms.WriteReader(fieldname, f.Name, int64(len(f.Bytes)), buf)
244 case FileReader:
245 if f.Size == -1 {
246 data, err := ioutil.ReadAll(f.Reader)
247 if err != nil {
248 return APIResponse{}, err
249 }
250 buf := bytes.NewBuffer(data)
251
252 ms.WriteReader(fieldname, f.Name, int64(len(data)), buf)
253
254 break
255 }
256
257 ms.WriteReader(fieldname, f.Name, f.Size, f.Reader)
258 default:
259 return APIResponse{}, errors.New("bad file type")
260 }
261
262 req, err := http.NewRequest("POST", fmt.Sprintf(APIEndpoint, bot.Token, endpoint), nil)
263 ms.SetupRequest(req)
264 if err != nil {
265 return APIResponse{}, err
266 }
267
268 res, err := bot.Client.Do(req)
269 if err != nil {
270 return APIResponse{}, err
271 }
272 defer res.Body.Close()
273
274 bytes, err := ioutil.ReadAll(res.Body)
275 if err != nil {
276 return APIResponse{}, err
277 }
278
279 if bot.Debug {
280 log.Println(string(bytes[:]))
281 }
282
283 var apiResp APIResponse
284 json.Unmarshal(bytes, &apiResp)
285
286 return apiResp, nil
287}
288
289// GetMe fetches the currently authenticated bot.
290//
291// There are no parameters for this method.
292func (bot *BotAPI) GetMe() (User, error) {
293 resp, err := bot.MakeRequest("getMe", nil)
294 if err != nil {
295 return User{}, err
296 }
297
298 var user User
299 json.Unmarshal(resp.Result, &user)
300
301 if bot.Debug {
302 log.Printf("getMe: %+v\n", user)
303 }
304
305 return user, nil
306}
307
308// SendMessage sends a Message to a chat.
309//
310// Requires ChatID and Text.
311// DisableWebPagePreview, ReplyToMessageID, and ReplyMarkup are optional.
312func (bot *BotAPI) SendMessage(config MessageConfig) (Message, error) {
313 v := url.Values{}
314 v.Add("chat_id", strconv.Itoa(config.ChatID))
315 v.Add("text", config.Text)
316 v.Add("disable_web_page_preview", strconv.FormatBool(config.DisableWebPagePreview))
317 if config.ParseMode != "" {
318 v.Add("parse_mode", config.ParseMode)
319 }
320 if config.ReplyToMessageID != 0 {
321 v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
322 }
323 if config.ReplyMarkup != nil {
324 data, err := json.Marshal(config.ReplyMarkup)
325 if err != nil {
326 return Message{}, err
327 }
328
329 v.Add("reply_markup", string(data))
330 }
331
332 resp, err := bot.MakeRequest("SendMessage", v)
333 if err != nil {
334 return Message{}, err
335 }
336
337 var message Message
338 json.Unmarshal(resp.Result, &message)
339
340 if bot.Debug {
341 log.Printf("SendMessage req : %+v\n", v)
342 log.Printf("SendMessage resp: %+v\n", message)
343 }
344
345 return message, nil
346}
347
348// ForwardMessage forwards a message from one chat to another.
349//
350// Requires ChatID (destination), FromChatID (source), and MessageID.
351func (bot *BotAPI) ForwardMessage(config ForwardConfig) (Message, error) {
352 v := url.Values{}
353 v.Add("chat_id", strconv.Itoa(config.ChatID))
354 v.Add("from_chat_id", strconv.Itoa(config.FromChatID))
355 v.Add("message_id", strconv.Itoa(config.MessageID))
356
357 resp, err := bot.MakeRequest("forwardMessage", v)
358 if err != nil {
359 return Message{}, err
360 }
361
362 var message Message
363 json.Unmarshal(resp.Result, &message)
364
365 if bot.Debug {
366 log.Printf("forwardMessage req : %+v\n", v)
367 log.Printf("forwardMessage resp: %+v\n", message)
368 }
369
370 return message, nil
371}
372
373// SendPhoto sends or uploads a photo to a chat.
374//
375// Requires ChatID and FileID OR File.
376// Caption, ReplyToMessageID, and ReplyMarkup are optional.
377// File should be either a string, FileBytes, or FileReader.
378func (bot *BotAPI) SendPhoto(config PhotoConfig) (Message, error) {
379 if config.UseExistingPhoto {
380 v := url.Values{}
381 v.Add("chat_id", strconv.Itoa(config.ChatID))
382 v.Add("photo", config.FileID)
383 if config.Caption != "" {
384 v.Add("caption", config.Caption)
385 }
386 if config.ReplyToMessageID != 0 {
387 v.Add("reply_to_message_id", strconv.Itoa(config.ChatID))
388 }
389 if config.ReplyMarkup != nil {
390 data, err := json.Marshal(config.ReplyMarkup)
391 if err != nil {
392 return Message{}, err
393 }
394
395 v.Add("reply_markup", string(data))
396 }
397
398 resp, err := bot.MakeRequest("SendPhoto", v)
399 if err != nil {
400 return Message{}, err
401 }
402
403 var message Message
404 json.Unmarshal(resp.Result, &message)
405
406 if bot.Debug {
407 log.Printf("SendPhoto req : %+v\n", v)
408 log.Printf("SendPhoto resp: %+v\n", message)
409 }
410
411 return message, nil
412 }
413
414 params := make(map[string]string)
415 params["chat_id"] = strconv.Itoa(config.ChatID)
416 if config.Caption != "" {
417 params["caption"] = config.Caption
418 }
419 if config.ReplyToMessageID != 0 {
420 params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
421 }
422 if config.ReplyMarkup != nil {
423 data, err := json.Marshal(config.ReplyMarkup)
424 if err != nil {
425 return Message{}, err
426 }
427
428 params["reply_markup"] = string(data)
429 }
430
431 var file interface{}
432 if config.FilePath == "" {
433 file = config.File
434 } else {
435 file = config.FilePath
436 }
437
438 resp, err := bot.UploadFile("SendPhoto", params, "photo", file)
439 if err != nil {
440 return Message{}, err
441 }
442
443 var message Message
444 json.Unmarshal(resp.Result, &message)
445
446 if bot.Debug {
447 log.Printf("SendPhoto resp: %+v\n", message)
448 }
449
450 return message, nil
451}
452
453// SendAudio sends or uploads an audio clip to a chat.
454// If using a file, the file must be in the .mp3 format.
455//
456// When the fields title and performer are both empty and
457// the mime-type of the file to be sent is not audio/mpeg,
458// the file must be an .ogg file encoded with OPUS.
459// You may use the tgutils.EncodeAudio func to assist you with this, if needed.
460//
461// Requires ChatID and FileID OR File.
462// ReplyToMessageID and ReplyMarkup are optional.
463// File should be either a string, FileBytes, or FileReader.
464func (bot *BotAPI) SendAudio(config AudioConfig) (Message, error) {
465 if config.UseExistingAudio {
466 v := url.Values{}
467 v.Add("chat_id", strconv.Itoa(config.ChatID))
468 v.Add("audio", config.FileID)
469 if config.ReplyToMessageID != 0 {
470 v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
471 }
472 if config.Duration != 0 {
473 v.Add("duration", strconv.Itoa(config.Duration))
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 if config.Performer != "" {
484 v.Add("performer", config.Performer)
485 }
486 if config.Title != "" {
487 v.Add("title", config.Title)
488 }
489
490 resp, err := bot.MakeRequest("sendAudio", v)
491 if err != nil {
492 return Message{}, err
493 }
494
495 var message Message
496 json.Unmarshal(resp.Result, &message)
497
498 if bot.Debug {
499 log.Printf("sendAudio req : %+v\n", v)
500 log.Printf("sendAudio resp: %+v\n", message)
501 }
502
503 return message, nil
504 }
505
506 params := make(map[string]string)
507
508 params["chat_id"] = strconv.Itoa(config.ChatID)
509 if config.ReplyToMessageID != 0 {
510 params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
511 }
512 if config.Duration != 0 {
513 params["duration"] = strconv.Itoa(config.Duration)
514 }
515 if config.ReplyMarkup != nil {
516 data, err := json.Marshal(config.ReplyMarkup)
517 if err != nil {
518 return Message{}, err
519 }
520
521 params["reply_markup"] = string(data)
522 }
523 if config.Performer != "" {
524 params["performer"] = config.Performer
525 }
526 if config.Title != "" {
527 params["title"] = config.Title
528 }
529
530 var file interface{}
531 if config.FilePath == "" {
532 file = config.File
533 } else {
534 file = config.FilePath
535 }
536
537 resp, err := bot.UploadFile("sendAudio", params, "audio", file)
538 if err != nil {
539 return Message{}, err
540 }
541
542 var message Message
543 json.Unmarshal(resp.Result, &message)
544
545 if bot.Debug {
546 log.Printf("sendAudio resp: %+v\n", message)
547 }
548
549 return message, nil
550}
551
552// SendDocument sends or uploads a document to a chat.
553//
554// Requires ChatID and FileID OR File.
555// ReplyToMessageID and ReplyMarkup are optional.
556// File should be either a string, FileBytes, or FileReader.
557func (bot *BotAPI) SendDocument(config DocumentConfig) (Message, error) {
558 if config.UseExistingDocument {
559 v := url.Values{}
560 v.Add("chat_id", strconv.Itoa(config.ChatID))
561 v.Add("document", config.FileID)
562 if config.ReplyToMessageID != 0 {
563 v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
564 }
565 if config.ReplyMarkup != nil {
566 data, err := json.Marshal(config.ReplyMarkup)
567 if err != nil {
568 return Message{}, err
569 }
570
571 v.Add("reply_markup", string(data))
572 }
573
574 resp, err := bot.MakeRequest("sendDocument", v)
575 if err != nil {
576 return Message{}, err
577 }
578
579 var message Message
580 json.Unmarshal(resp.Result, &message)
581
582 if bot.Debug {
583 log.Printf("sendDocument req : %+v\n", v)
584 log.Printf("sendDocument resp: %+v\n", message)
585 }
586
587 return message, nil
588 }
589
590 params := make(map[string]string)
591
592 params["chat_id"] = strconv.Itoa(config.ChatID)
593 if config.ReplyToMessageID != 0 {
594 params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
595 }
596 if config.ReplyMarkup != nil {
597 data, err := json.Marshal(config.ReplyMarkup)
598 if err != nil {
599 return Message{}, err
600 }
601
602 params["reply_markup"] = string(data)
603 }
604
605 var file interface{}
606 if config.FilePath == "" {
607 file = config.File
608 } else {
609 file = config.FilePath
610 }
611
612 resp, err := bot.UploadFile("sendDocument", params, "document", file)
613 if err != nil {
614 return Message{}, err
615 }
616
617 var message Message
618 json.Unmarshal(resp.Result, &message)
619
620 if bot.Debug {
621 log.Printf("sendDocument resp: %+v\n", message)
622 }
623
624 return message, nil
625}
626
627// SendVoice sends or uploads a playable voice to a chat.
628// If using a file, the file must be encoded as an .ogg with OPUS.
629// You may use the tgutils.EncodeAudio func to assist you with this, if needed.
630//
631// Requires ChatID and FileID OR File.
632// ReplyToMessageID and ReplyMarkup are optional.
633// File should be either a string, FileBytes, or FileReader.
634func (bot *BotAPI) SendVoice(config VoiceConfig) (Message, error) {
635 if config.UseExistingVoice {
636 v := url.Values{}
637 v.Add("chat_id", strconv.Itoa(config.ChatID))
638 v.Add("voice", config.FileID)
639 if config.ReplyToMessageID != 0 {
640 v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
641 }
642 if config.Duration != 0 {
643 v.Add("duration", strconv.Itoa(config.Duration))
644 }
645 if config.ReplyMarkup != nil {
646 data, err := json.Marshal(config.ReplyMarkup)
647 if err != nil {
648 return Message{}, err
649 }
650
651 v.Add("reply_markup", string(data))
652 }
653
654 resp, err := bot.MakeRequest("sendVoice", v)
655 if err != nil {
656 return Message{}, err
657 }
658
659 var message Message
660 json.Unmarshal(resp.Result, &message)
661
662 if bot.Debug {
663 log.Printf("SendVoice req : %+v\n", v)
664 log.Printf("SendVoice resp: %+v\n", message)
665 }
666
667 return message, nil
668 }
669
670 params := make(map[string]string)
671
672 params["chat_id"] = strconv.Itoa(config.ChatID)
673 if config.ReplyToMessageID != 0 {
674 params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
675 }
676 if config.Duration != 0 {
677 params["duration"] = strconv.Itoa(config.Duration)
678 }
679 if config.ReplyMarkup != nil {
680 data, err := json.Marshal(config.ReplyMarkup)
681 if err != nil {
682 return Message{}, err
683 }
684
685 params["reply_markup"] = string(data)
686 }
687
688 var file interface{}
689 if config.FilePath == "" {
690 file = config.File
691 } else {
692 file = config.FilePath
693 }
694
695 resp, err := bot.UploadFile("SendVoice", params, "voice", file)
696 if err != nil {
697 return Message{}, err
698 }
699
700 var message Message
701 json.Unmarshal(resp.Result, &message)
702
703 if bot.Debug {
704 log.Printf("SendVoice resp: %+v\n", message)
705 }
706
707 return message, nil
708}
709
710// SendSticker sends or uploads a sticker to a chat.
711//
712// Requires ChatID and FileID OR File.
713// ReplyToMessageID and ReplyMarkup are optional.
714// File should be either a string, FileBytes, or FileReader.
715func (bot *BotAPI) SendSticker(config StickerConfig) (Message, error) {
716 if config.UseExistingSticker {
717 v := url.Values{}
718 v.Add("chat_id", strconv.Itoa(config.ChatID))
719 v.Add("sticker", config.FileID)
720 if config.ReplyToMessageID != 0 {
721 v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
722 }
723 if config.ReplyMarkup != nil {
724 data, err := json.Marshal(config.ReplyMarkup)
725 if err != nil {
726 return Message{}, err
727 }
728
729 v.Add("reply_markup", string(data))
730 }
731
732 resp, err := bot.MakeRequest("sendSticker", v)
733 if err != nil {
734 return Message{}, err
735 }
736
737 var message Message
738 json.Unmarshal(resp.Result, &message)
739
740 if bot.Debug {
741 log.Printf("sendSticker req : %+v\n", v)
742 log.Printf("sendSticker resp: %+v\n", message)
743 }
744
745 return message, nil
746 }
747
748 params := make(map[string]string)
749
750 params["chat_id"] = strconv.Itoa(config.ChatID)
751 if config.ReplyToMessageID != 0 {
752 params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
753 }
754 if config.ReplyMarkup != nil {
755 data, err := json.Marshal(config.ReplyMarkup)
756 if err != nil {
757 return Message{}, err
758 }
759
760 params["reply_markup"] = string(data)
761 }
762
763 var file interface{}
764 if config.FilePath == "" {
765 file = config.File
766 } else {
767 file = config.FilePath
768 }
769
770 resp, err := bot.UploadFile("sendSticker", params, "sticker", file)
771 if err != nil {
772 return Message{}, err
773 }
774
775 var message Message
776 json.Unmarshal(resp.Result, &message)
777
778 if bot.Debug {
779 log.Printf("sendSticker resp: %+v\n", message)
780 }
781
782 return message, nil
783}
784
785// SendVideo sends or uploads a video to a chat.
786//
787// Requires ChatID and FileID OR File.
788// ReplyToMessageID and ReplyMarkup are optional.
789// File should be either a string, FileBytes, or FileReader.
790func (bot *BotAPI) SendVideo(config VideoConfig) (Message, error) {
791 if config.UseExistingVideo {
792 v := url.Values{}
793 v.Add("chat_id", strconv.Itoa(config.ChatID))
794 v.Add("video", config.FileID)
795 if config.ReplyToMessageID != 0 {
796 v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
797 }
798 if config.Duration != 0 {
799 v.Add("duration", strconv.Itoa(config.Duration))
800 }
801 if config.Caption != "" {
802 v.Add("caption", config.Caption)
803 }
804 if config.ReplyMarkup != nil {
805 data, err := json.Marshal(config.ReplyMarkup)
806 if err != nil {
807 return Message{}, err
808 }
809
810 v.Add("reply_markup", string(data))
811 }
812
813 resp, err := bot.MakeRequest("sendVideo", v)
814 if err != nil {
815 return Message{}, err
816 }
817
818 var message Message
819 json.Unmarshal(resp.Result, &message)
820
821 if bot.Debug {
822 log.Printf("sendVideo req : %+v\n", v)
823 log.Printf("sendVideo resp: %+v\n", message)
824 }
825
826 return message, nil
827 }
828
829 params := make(map[string]string)
830
831 params["chat_id"] = strconv.Itoa(config.ChatID)
832 if config.ReplyToMessageID != 0 {
833 params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
834 }
835 if config.ReplyMarkup != nil {
836 data, err := json.Marshal(config.ReplyMarkup)
837 if err != nil {
838 return Message{}, err
839 }
840
841 params["reply_markup"] = string(data)
842 }
843
844 var file interface{}
845 if config.FilePath == "" {
846 file = config.File
847 } else {
848 file = config.FilePath
849 }
850
851 resp, err := bot.UploadFile("sendVideo", params, "video", file)
852 if err != nil {
853 return Message{}, err
854 }
855
856 var message Message
857 json.Unmarshal(resp.Result, &message)
858
859 if bot.Debug {
860 log.Printf("sendVideo resp: %+v\n", message)
861 }
862
863 return message, nil
864}
865
866// SendLocation sends a location to a chat.
867//
868// Requires ChatID, Latitude, and Longitude.
869// ReplyToMessageID and ReplyMarkup are optional.
870func (bot *BotAPI) SendLocation(config LocationConfig) (Message, error) {
871 v := url.Values{}
872 v.Add("chat_id", strconv.Itoa(config.ChatID))
873 v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64))
874 v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64))
875 if config.ReplyToMessageID != 0 {
876 v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
877 }
878 if config.ReplyMarkup != nil {
879 data, err := json.Marshal(config.ReplyMarkup)
880 if err != nil {
881 return Message{}, err
882 }
883
884 v.Add("reply_markup", string(data))
885 }
886
887 resp, err := bot.MakeRequest("sendLocation", v)
888 if err != nil {
889 return Message{}, err
890 }
891
892 var message Message
893 json.Unmarshal(resp.Result, &message)
894
895 if bot.Debug {
896 log.Printf("sendLocation req : %+v\n", v)
897 log.Printf("sendLocation resp: %+v\n", message)
898 }
899
900 return message, nil
901}
902
903// SendChatAction sets a current action in a chat.
904//
905// Requires ChatID and a valid Action (see Chat constants).
906func (bot *BotAPI) SendChatAction(config ChatActionConfig) error {
907 v := url.Values{}
908 v.Add("chat_id", strconv.Itoa(config.ChatID))
909 v.Add("action", config.Action)
910
911 _, err := bot.MakeRequest("sendChatAction", v)
912 if err != nil {
913 return err
914 }
915
916 return nil
917}
918
919// GetUserProfilePhotos gets a user's profile photos.
920//
921// Requires UserID.
922// Offset and Limit are optional.
923func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) {
924 v := url.Values{}
925 v.Add("user_id", strconv.Itoa(config.UserID))
926 if config.Offset != 0 {
927 v.Add("offset", strconv.Itoa(config.Offset))
928 }
929 if config.Limit != 0 {
930 v.Add("limit", strconv.Itoa(config.Limit))
931 }
932
933 resp, err := bot.MakeRequest("getUserProfilePhotos", v)
934 if err != nil {
935 return UserProfilePhotos{}, err
936 }
937
938 var profilePhotos UserProfilePhotos
939 json.Unmarshal(resp.Result, &profilePhotos)
940
941 if bot.Debug {
942 log.Printf("getUserProfilePhotos req : %+v\n", v)
943 log.Printf("getUserProfilePhotos resp: %+v\n", profilePhotos)
944 }
945
946 return profilePhotos, nil
947}
948
949// GetUpdates fetches updates.
950// If a WebHook is set, this will not return any data!
951//
952// Offset, Limit, and Timeout are optional.
953// To not get old items, set Offset to one higher than the previous item.
954// Set Timeout to a large number to reduce requests and get responses instantly.
955func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) {
956 v := url.Values{}
957 if config.Offset > 0 {
958 v.Add("offset", strconv.Itoa(config.Offset))
959 }
960 if config.Limit > 0 {
961 v.Add("limit", strconv.Itoa(config.Limit))
962 }
963 if config.Timeout > 0 {
964 v.Add("timeout", strconv.Itoa(config.Timeout))
965 }
966
967 resp, err := bot.MakeRequest("getUpdates", v)
968 if err != nil {
969 return []Update{}, err
970 }
971
972 var updates []Update
973 json.Unmarshal(resp.Result, &updates)
974
975 if bot.Debug {
976 log.Printf("getUpdates: %+v\n", updates)
977 }
978
979 return updates, nil
980}
981
982// SetWebhook sets a webhook.
983// If this is set, GetUpdates will not get any data!
984//
985// Requires Url OR to set Clear to true.
986func (bot *BotAPI) SetWebhook(config WebhookConfig) (APIResponse, error) {
987 if config.Certificate == nil {
988 v := url.Values{}
989 if !config.Clear {
990 v.Add("url", config.URL.String())
991 }
992
993 return bot.MakeRequest("setWebhook", v)
994 }
995
996 params := make(map[string]string)
997 params["url"] = config.URL.String()
998
999 resp, err := bot.UploadFile("setWebhook", params, "certificate", config.Certificate)
1000 if err != nil {
1001 return APIResponse{}, err
1002 }
1003
1004 var apiResp APIResponse
1005 json.Unmarshal(resp.Result, &apiResp)
1006
1007 if bot.Debug {
1008 log.Printf("setWebhook resp: %+v\n", apiResp)
1009 }
1010
1011 return apiResp, nil
1012}