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