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