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