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