all repos — telegram-bot-api @ e48e6416e7805cf7a96c33c6eb958b94131585e1

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