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