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