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 := http.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 := http.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 client := &http.Client{}
201 res, err := client.Do(req)
202 if err != nil {
203 return APIResponse{}, err
204 }
205
206 bytes, err := ioutil.ReadAll(res.Body)
207 if err != nil {
208 return APIResponse{}, err
209 }
210
211 if bot.Debug {
212 log.Println(string(bytes[:]))
213 }
214
215 var apiResp APIResponse
216 json.Unmarshal(bytes, &apiResp)
217
218 return apiResp, nil
219}
220
221// GetMe fetches the currently authenticated bot.
222//
223// There are no parameters for this method.
224func (bot *BotAPI) GetMe() (User, error) {
225 resp, err := bot.MakeRequest("getMe", nil)
226 if err != nil {
227 return User{}, err
228 }
229
230 var user User
231 json.Unmarshal(resp.Result, &user)
232
233 if bot.Debug {
234 log.Printf("getMe: %+v\n", user)
235 }
236
237 return user, nil
238}
239
240// SendMessage sends a Message to a chat.
241//
242// Requires ChatID and Text.
243// DisableWebPagePreview, ReplyToMessageID, and ReplyMarkup are optional.
244func (bot *BotAPI) SendMessage(config MessageConfig) (Message, error) {
245 v := url.Values{}
246 v.Add("chat_id", strconv.Itoa(config.ChatID))
247 v.Add("text", config.Text)
248 v.Add("disable_web_page_preview", strconv.FormatBool(config.DisableWebPagePreview))
249 if config.ReplyToMessageID != 0 {
250 v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
251 }
252 if config.ReplyMarkup != nil {
253 data, err := json.Marshal(config.ReplyMarkup)
254 if err != nil {
255 return Message{}, err
256 }
257
258 v.Add("reply_markup", string(data))
259 }
260
261 resp, err := bot.MakeRequest("SendMessage", v)
262 if err != nil {
263 return Message{}, err
264 }
265
266 var message Message
267 json.Unmarshal(resp.Result, &message)
268
269 if bot.Debug {
270 log.Printf("SendMessage req : %+v\n", v)
271 log.Printf("SendMessage resp: %+v\n", message)
272 }
273
274 return message, nil
275}
276
277// ForwardMessage forwards a message from one chat to another.
278//
279// Requires ChatID (destionation), FromChatID (source), and MessageID.
280func (bot *BotAPI) ForwardMessage(config ForwardConfig) (Message, error) {
281 v := url.Values{}
282 v.Add("chat_id", strconv.Itoa(config.ChatID))
283 v.Add("from_chat_id", strconv.Itoa(config.FromChatID))
284 v.Add("message_id", strconv.Itoa(config.MessageID))
285
286 resp, err := bot.MakeRequest("forwardMessage", v)
287 if err != nil {
288 return Message{}, err
289 }
290
291 var message Message
292 json.Unmarshal(resp.Result, &message)
293
294 if bot.Debug {
295 log.Printf("forwardMessage req : %+v\n", v)
296 log.Printf("forwardMessage resp: %+v\n", message)
297 }
298
299 return message, nil
300}
301
302// SendPhoto sends or uploads a photo to a chat.
303//
304// Requires ChatID and FileID OR FilePath.
305// Caption, ReplyToMessageID, and ReplyMarkup are optional.
306func (bot *BotAPI) SendPhoto(config PhotoConfig) (Message, error) {
307 if config.UseExistingPhoto {
308 v := url.Values{}
309 v.Add("chat_id", strconv.Itoa(config.ChatID))
310 v.Add("photo", config.FileID)
311 if config.Caption != "" {
312 v.Add("caption", config.Caption)
313 }
314 if config.ReplyToMessageID != 0 {
315 v.Add("reply_to_message_id", strconv.Itoa(config.ChatID))
316 }
317 if config.ReplyMarkup != nil {
318 data, err := json.Marshal(config.ReplyMarkup)
319 if err != nil {
320 return Message{}, err
321 }
322
323 v.Add("reply_markup", string(data))
324 }
325
326 resp, err := bot.MakeRequest("SendPhoto", v)
327 if err != nil {
328 return Message{}, err
329 }
330
331 var message Message
332 json.Unmarshal(resp.Result, &message)
333
334 if bot.Debug {
335 log.Printf("SendPhoto req : %+v\n", v)
336 log.Printf("SendPhoto resp: %+v\n", message)
337 }
338
339 return message, nil
340 }
341
342 params := make(map[string]string)
343 params["chat_id"] = strconv.Itoa(config.ChatID)
344 if config.Caption != "" {
345 params["caption"] = config.Caption
346 }
347 if config.ReplyToMessageID != 0 {
348 params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
349 }
350 if config.ReplyMarkup != nil {
351 data, err := json.Marshal(config.ReplyMarkup)
352 if err != nil {
353 return Message{}, err
354 }
355
356 params["reply_markup"] = string(data)
357 }
358
359 resp, err := bot.UploadFile("SendPhoto", params, "photo", config.FilePath)
360 if err != nil {
361 return Message{}, err
362 }
363
364 var message Message
365 json.Unmarshal(resp.Result, &message)
366
367 if bot.Debug {
368 log.Printf("SendPhoto resp: %+v\n", message)
369 }
370
371 return message, nil
372}
373
374// SendAudio sends or uploads an audio clip to a chat.
375// If using a file, the file must be encoded as an .ogg with OPUS.
376//
377// Requires ChatID and FileID OR FilePath.
378// ReplyToMessageID and ReplyMarkup are optional.
379func (bot *BotAPI) SendAudio(config AudioConfig) (Message, error) {
380 if config.UseExistingAudio {
381 v := url.Values{}
382 v.Add("chat_id", strconv.Itoa(config.ChatID))
383 v.Add("audio", config.FileID)
384 if config.ReplyToMessageID != 0 {
385 v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
386 }
387 if config.ReplyMarkup != nil {
388 data, err := json.Marshal(config.ReplyMarkup)
389 if err != nil {
390 return Message{}, err
391 }
392
393 v.Add("reply_markup", string(data))
394 }
395
396 resp, err := bot.MakeRequest("sendAudio", v)
397 if err != nil {
398 return Message{}, err
399 }
400
401 var message Message
402 json.Unmarshal(resp.Result, &message)
403
404 if bot.Debug {
405 log.Printf("sendAudio req : %+v\n", v)
406 log.Printf("sendAudio resp: %+v\n", message)
407 }
408
409 return message, nil
410 }
411
412 params := make(map[string]string)
413
414 params["chat_id"] = strconv.Itoa(config.ChatID)
415 if config.ReplyToMessageID != 0 {
416 params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
417 }
418 if config.ReplyMarkup != nil {
419 data, err := json.Marshal(config.ReplyMarkup)
420 if err != nil {
421 return Message{}, err
422 }
423
424 params["reply_markup"] = string(data)
425 }
426
427 resp, err := bot.UploadFile("sendAudio", params, "audio", config.FilePath)
428 if err != nil {
429 return Message{}, err
430 }
431
432 var message Message
433 json.Unmarshal(resp.Result, &message)
434
435 if bot.Debug {
436 log.Printf("sendAudio resp: %+v\n", message)
437 }
438
439 return message, nil
440}
441
442// SendDocument sends or uploads a document to a chat.
443//
444// Requires ChatID and FileID OR FilePath.
445// ReplyToMessageID and ReplyMarkup are optional.
446func (bot *BotAPI) SendDocument(config DocumentConfig) (Message, error) {
447 if config.UseExistingDocument {
448 v := url.Values{}
449 v.Add("chat_id", strconv.Itoa(config.ChatID))
450 v.Add("document", config.FileID)
451 if config.ReplyToMessageID != 0 {
452 v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
453 }
454 if config.ReplyMarkup != nil {
455 data, err := json.Marshal(config.ReplyMarkup)
456 if err != nil {
457 return Message{}, err
458 }
459
460 v.Add("reply_markup", string(data))
461 }
462
463 resp, err := bot.MakeRequest("sendDocument", v)
464 if err != nil {
465 return Message{}, err
466 }
467
468 var message Message
469 json.Unmarshal(resp.Result, &message)
470
471 if bot.Debug {
472 log.Printf("sendDocument req : %+v\n", v)
473 log.Printf("sendDocument resp: %+v\n", message)
474 }
475
476 return message, nil
477 }
478
479 params := make(map[string]string)
480
481 params["chat_id"] = strconv.Itoa(config.ChatID)
482 if config.ReplyToMessageID != 0 {
483 params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
484 }
485 if config.ReplyMarkup != nil {
486 data, err := json.Marshal(config.ReplyMarkup)
487 if err != nil {
488 return Message{}, err
489 }
490
491 params["reply_markup"] = string(data)
492 }
493
494 resp, err := bot.UploadFile("sendDocument", params, "document", config.FilePath)
495 if err != nil {
496 return Message{}, err
497 }
498
499 var message Message
500 json.Unmarshal(resp.Result, &message)
501
502 if bot.Debug {
503 log.Printf("sendDocument resp: %+v\n", message)
504 }
505
506 return message, nil
507}
508
509// SendSticker sends or uploads a sticker to a chat.
510//
511// Requires ChatID and FileID OR FilePath.
512// ReplyToMessageID and ReplyMarkup are optional.
513func (bot *BotAPI) SendSticker(config StickerConfig) (Message, error) {
514 if config.UseExistingSticker {
515 v := url.Values{}
516 v.Add("chat_id", strconv.Itoa(config.ChatID))
517 v.Add("sticker", config.FileID)
518 if config.ReplyToMessageID != 0 {
519 v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
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
530 resp, err := bot.MakeRequest("sendSticker", v)
531 if err != nil {
532 return Message{}, err
533 }
534
535 var message Message
536 json.Unmarshal(resp.Result, &message)
537
538 if bot.Debug {
539 log.Printf("sendSticker req : %+v\n", v)
540 log.Printf("sendSticker resp: %+v\n", message)
541 }
542
543 return message, nil
544 }
545
546 params := make(map[string]string)
547
548 params["chat_id"] = strconv.Itoa(config.ChatID)
549 if config.ReplyToMessageID != 0 {
550 params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
551 }
552 if config.ReplyMarkup != nil {
553 data, err := json.Marshal(config.ReplyMarkup)
554 if err != nil {
555 return Message{}, err
556 }
557
558 params["reply_markup"] = string(data)
559 }
560
561 resp, err := bot.UploadFile("sendSticker", params, "sticker", config.FilePath)
562 if err != nil {
563 return Message{}, err
564 }
565
566 var message Message
567 json.Unmarshal(resp.Result, &message)
568
569 if bot.Debug {
570 log.Printf("sendSticker resp: %+v\n", message)
571 }
572
573 return message, nil
574}
575
576// SendVideo sends or uploads a video to a chat.
577//
578// Requires ChatID and FileID OR FilePath.
579// ReplyToMessageID and ReplyMarkup are optional.
580func (bot *BotAPI) SendVideo(config VideoConfig) (Message, error) {
581 if config.UseExistingVideo {
582 v := url.Values{}
583 v.Add("chat_id", strconv.Itoa(config.ChatID))
584 v.Add("video", config.FileID)
585 if config.ReplyToMessageID != 0 {
586 v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
587 }
588 if config.ReplyMarkup != nil {
589 data, err := json.Marshal(config.ReplyMarkup)
590 if err != nil {
591 return Message{}, err
592 }
593
594 v.Add("reply_markup", string(data))
595 }
596
597 resp, err := bot.MakeRequest("sendVideo", v)
598 if err != nil {
599 return Message{}, err
600 }
601
602 var message Message
603 json.Unmarshal(resp.Result, &message)
604
605 if bot.Debug {
606 log.Printf("sendVideo req : %+v\n", v)
607 log.Printf("sendVideo resp: %+v\n", message)
608 }
609
610 return message, nil
611 }
612
613 params := make(map[string]string)
614
615 params["chat_id"] = strconv.Itoa(config.ChatID)
616 if config.ReplyToMessageID != 0 {
617 params["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 params["reply_markup"] = string(data)
626 }
627
628 resp, err := bot.UploadFile("sendVideo", params, "video", config.FilePath)
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("sendVideo resp: %+v\n", message)
638 }
639
640 return message, nil
641}
642
643// SendLocation sends a location to a chat.
644//
645// Requires ChatID, Latitude, and Longitude.
646// ReplyToMessageID and ReplyMarkup are optional.
647func (bot *BotAPI) SendLocation(config LocationConfig) (Message, error) {
648 v := url.Values{}
649 v.Add("chat_id", strconv.Itoa(config.ChatID))
650 v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64))
651 v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64))
652 if config.ReplyToMessageID != 0 {
653 v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
654 }
655 if config.ReplyMarkup != nil {
656 data, err := json.Marshal(config.ReplyMarkup)
657 if err != nil {
658 return Message{}, err
659 }
660
661 v.Add("reply_markup", string(data))
662 }
663
664 resp, err := bot.MakeRequest("sendLocation", v)
665 if err != nil {
666 return Message{}, err
667 }
668
669 var message Message
670 json.Unmarshal(resp.Result, &message)
671
672 if bot.Debug {
673 log.Printf("sendLocation req : %+v\n", v)
674 log.Printf("sendLocation resp: %+v\n", message)
675 }
676
677 return message, nil
678}
679
680// SendChatAction sets a current action in a chat.
681//
682// Requires ChatID and a valid Action (see Chat constants).
683func (bot *BotAPI) SendChatAction(config ChatActionConfig) error {
684 v := url.Values{}
685 v.Add("chat_id", strconv.Itoa(config.ChatID))
686 v.Add("action", config.Action)
687
688 _, err := bot.MakeRequest("sendChatAction", v)
689 if err != nil {
690 return err
691 }
692
693 return nil
694}
695
696// GetUserProfilePhotos gets a user's profile photos.
697//
698// Requires UserID.
699// Offset and Limit are optional.
700func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) {
701 v := url.Values{}
702 v.Add("user_id", strconv.Itoa(config.UserID))
703 if config.Offset != 0 {
704 v.Add("offset", strconv.Itoa(config.Offset))
705 }
706 if config.Limit != 0 {
707 v.Add("limit", strconv.Itoa(config.Limit))
708 }
709
710 resp, err := bot.MakeRequest("getUserProfilePhotos", v)
711 if err != nil {
712 return UserProfilePhotos{}, err
713 }
714
715 var profilePhotos UserProfilePhotos
716 json.Unmarshal(resp.Result, &profilePhotos)
717
718 if bot.Debug {
719 log.Printf("getUserProfilePhotos req : %+v\n", v)
720 log.Printf("getUserProfilePhotos resp: %+v\n", profilePhotos)
721 }
722
723 return profilePhotos, nil
724}
725
726// GetUpdates fetches updates.
727// If a WebHook is set, this will not return any data!
728//
729// Offset, Limit, and Timeout are optional.
730// To not get old items, set Offset to one higher than the previous item.
731// Set Timeout to a large number to reduce requests and get responses instantly.
732func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) {
733 v := url.Values{}
734 if config.Offset > 0 {
735 v.Add("offset", strconv.Itoa(config.Offset))
736 }
737 if config.Limit > 0 {
738 v.Add("limit", strconv.Itoa(config.Limit))
739 }
740 if config.Timeout > 0 {
741 v.Add("timeout", strconv.Itoa(config.Timeout))
742 }
743
744 resp, err := bot.MakeRequest("getUpdates", v)
745 if err != nil {
746 return []Update{}, err
747 }
748
749 var updates []Update
750 json.Unmarshal(resp.Result, &updates)
751
752 if bot.Debug {
753 log.Printf("getUpdates: %+v\n", updates)
754 }
755
756 return updates, nil
757}
758
759// SetWebhook sets a webhook.
760// If this is set, GetUpdates will not get any data!
761//
762// Requires Url OR to set Clear to true.
763func (bot *BotAPI) SetWebhook(config WebhookConfig) error {
764 v := url.Values{}
765 if !config.Clear {
766 v.Add("url", config.URL.String())
767 }
768
769 _, err := bot.MakeRequest("setWebhook", v)
770
771 return err
772}