all repos — telegram-bot-api @ b2d2f4f5b0280ee1fd44e45a4c56619f1d5a5ac9

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