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