all repos — telegram-bot-api @ d1358d12aaa4207728e8a60aa1c62b670611f9c3

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