all repos — telegram-bot-api @ 0d2feed6c26a7bd6c5458e970afd1f72a21d0dd8

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