all repos — telegram-bot-api @ d537d26a2b450cf4914637be36a02f3335b71593

Golang bindings for the Telegram Bot API

methods.go (view raw)

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