all repos — telegram-bot-api @ 8e71d1db32b3d00bb003300b425ae3f82c25617b

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	return apiResp, nil
 294}
 295
 296// GetMe fetches the currently authenticated bot.
 297//
 298// There are no parameters for this method.
 299func (bot *BotAPI) GetMe() (User, error) {
 300	resp, err := bot.MakeRequest("getMe", nil)
 301	if err != nil {
 302		return User{}, err
 303	}
 304
 305	var user User
 306	json.Unmarshal(resp.Result, &user)
 307
 308	if bot.Debug {
 309		log.Printf("getMe: %+v\n", user)
 310	}
 311
 312	return user, nil
 313}
 314
 315// SendMessage sends a Message to a chat.
 316//
 317// Requires ChatID and Text.
 318// DisableWebPagePreview, ReplyToMessageID, and ReplyMarkup are optional.
 319func (bot *BotAPI) SendMessage(config MessageConfig) (Message, error) {
 320	v := url.Values{}
 321	v.Add("chat_id", strconv.Itoa(config.ChatID))
 322	v.Add("text", config.Text)
 323	v.Add("disable_web_page_preview", strconv.FormatBool(config.DisableWebPagePreview))
 324	if config.ParseMode != "" {
 325		v.Add("parse_mode", config.ParseMode)
 326	}
 327	if config.ReplyToMessageID != 0 {
 328		v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
 329	}
 330	if config.ReplyMarkup != nil {
 331		data, err := json.Marshal(config.ReplyMarkup)
 332		if err != nil {
 333			return Message{}, err
 334		}
 335
 336		v.Add("reply_markup", string(data))
 337	}
 338
 339	resp, err := bot.MakeRequest("SendMessage", v)
 340	if err != nil {
 341		return Message{}, err
 342	}
 343
 344	var message Message
 345	json.Unmarshal(resp.Result, &message)
 346
 347	if bot.Debug {
 348		log.Printf("SendMessage req : %+v\n", v)
 349		log.Printf("SendMessage resp: %+v\n", message)
 350	}
 351
 352	return message, nil
 353}
 354
 355// ForwardMessage forwards a message from one chat to another.
 356//
 357// Requires ChatID (destination), FromChatID (source), and MessageID.
 358func (bot *BotAPI) ForwardMessage(config ForwardConfig) (Message, error) {
 359	v := url.Values{}
 360	v.Add("chat_id", strconv.Itoa(config.ChatID))
 361	v.Add("from_chat_id", strconv.Itoa(config.FromChatID))
 362	v.Add("message_id", strconv.Itoa(config.MessageID))
 363
 364	resp, err := bot.MakeRequest("forwardMessage", v)
 365	if err != nil {
 366		return Message{}, err
 367	}
 368
 369	var message Message
 370	json.Unmarshal(resp.Result, &message)
 371
 372	if bot.Debug {
 373		log.Printf("forwardMessage req : %+v\n", v)
 374		log.Printf("forwardMessage resp: %+v\n", message)
 375	}
 376
 377	return message, nil
 378}
 379
 380// SendPhoto sends or uploads a photo to a chat.
 381//
 382// Requires ChatID and FileID OR File.
 383// Caption, ReplyToMessageID, and ReplyMarkup are optional.
 384// File should be either a string, FileBytes, or FileReader.
 385func (bot *BotAPI) SendPhoto(config PhotoConfig) (Message, error) {
 386	if config.UseExistingPhoto {
 387		v := url.Values{}
 388		v.Add("chat_id", strconv.Itoa(config.ChatID))
 389		v.Add("photo", config.FileID)
 390		if config.Caption != "" {
 391			v.Add("caption", config.Caption)
 392		}
 393		if config.ReplyToMessageID != 0 {
 394			v.Add("reply_to_message_id", strconv.Itoa(config.ChatID))
 395		}
 396		if config.ReplyMarkup != nil {
 397			data, err := json.Marshal(config.ReplyMarkup)
 398			if err != nil {
 399				return Message{}, err
 400			}
 401
 402			v.Add("reply_markup", string(data))
 403		}
 404
 405		resp, err := bot.MakeRequest("SendPhoto", v)
 406		if err != nil {
 407			return Message{}, err
 408		}
 409
 410		var message Message
 411		json.Unmarshal(resp.Result, &message)
 412
 413		if bot.Debug {
 414			log.Printf("SendPhoto req : %+v\n", v)
 415			log.Printf("SendPhoto resp: %+v\n", message)
 416		}
 417
 418		return message, nil
 419	}
 420
 421	params := make(map[string]string)
 422	params["chat_id"] = strconv.Itoa(config.ChatID)
 423	if config.Caption != "" {
 424		params["caption"] = config.Caption
 425	}
 426	if config.ReplyToMessageID != 0 {
 427		params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
 428	}
 429	if config.ReplyMarkup != nil {
 430		data, err := json.Marshal(config.ReplyMarkup)
 431		if err != nil {
 432			return Message{}, err
 433		}
 434
 435		params["reply_markup"] = string(data)
 436	}
 437
 438	var file interface{}
 439	if config.FilePath == "" {
 440		file = config.File
 441	} else {
 442		file = config.FilePath
 443	}
 444
 445	resp, err := bot.UploadFile("SendPhoto", params, "photo", file)
 446	if err != nil {
 447		return Message{}, err
 448	}
 449
 450	var message Message
 451	json.Unmarshal(resp.Result, &message)
 452
 453	if bot.Debug {
 454		log.Printf("SendPhoto resp: %+v\n", message)
 455	}
 456
 457	return message, nil
 458}
 459
 460// SendAudio sends or uploads an audio clip to a chat.
 461// If using a file, the file must be in the .mp3 format.
 462//
 463// When the fields title and performer are both empty and
 464// the mime-type of the file to be sent is not audio/mpeg,
 465// the file must be an .ogg file encoded with OPUS.
 466// You may use the tgutils.EncodeAudio func to assist you with this, if needed.
 467//
 468// Requires ChatID and FileID OR File.
 469// ReplyToMessageID and ReplyMarkup are optional.
 470// File should be either a string, FileBytes, or FileReader.
 471func (bot *BotAPI) SendAudio(config AudioConfig) (Message, error) {
 472	if config.UseExistingAudio {
 473		v := url.Values{}
 474		v.Add("chat_id", strconv.Itoa(config.ChatID))
 475		v.Add("audio", config.FileID)
 476		if config.ReplyToMessageID != 0 {
 477			v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
 478		}
 479		if config.Duration != 0 {
 480			v.Add("duration", strconv.Itoa(config.Duration))
 481		}
 482		if config.ReplyMarkup != nil {
 483			data, err := json.Marshal(config.ReplyMarkup)
 484			if err != nil {
 485				return Message{}, err
 486			}
 487
 488			v.Add("reply_markup", string(data))
 489		}
 490		if config.Performer != "" {
 491			v.Add("performer", config.Performer)
 492		}
 493		if config.Title != "" {
 494			v.Add("title", config.Title)
 495		}
 496
 497		resp, err := bot.MakeRequest("sendAudio", v)
 498		if err != nil {
 499			return Message{}, err
 500		}
 501
 502		var message Message
 503		json.Unmarshal(resp.Result, &message)
 504
 505		if bot.Debug {
 506			log.Printf("sendAudio req : %+v\n", v)
 507			log.Printf("sendAudio resp: %+v\n", message)
 508		}
 509
 510		return message, nil
 511	}
 512
 513	params := make(map[string]string)
 514
 515	params["chat_id"] = strconv.Itoa(config.ChatID)
 516	if config.ReplyToMessageID != 0 {
 517		params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
 518	}
 519	if config.Duration != 0 {
 520		params["duration"] = strconv.Itoa(config.Duration)
 521	}
 522	if config.ReplyMarkup != nil {
 523		data, err := json.Marshal(config.ReplyMarkup)
 524		if err != nil {
 525			return Message{}, err
 526		}
 527
 528		params["reply_markup"] = string(data)
 529	}
 530	if config.Performer != "" {
 531		params["performer"] = config.Performer
 532	}
 533	if config.Title != "" {
 534		params["title"] = config.Title
 535	}
 536
 537	var file interface{}
 538	if config.FilePath == "" {
 539		file = config.File
 540	} else {
 541		file = config.FilePath
 542	}
 543
 544	resp, err := bot.UploadFile("sendAudio", params, "audio", file)
 545	if err != nil {
 546		return Message{}, err
 547	}
 548
 549	var message Message
 550	json.Unmarshal(resp.Result, &message)
 551
 552	if bot.Debug {
 553		log.Printf("sendAudio resp: %+v\n", message)
 554	}
 555
 556	return message, nil
 557}
 558
 559// SendDocument sends or uploads a document to a chat.
 560//
 561// Requires ChatID and FileID OR File.
 562// ReplyToMessageID and ReplyMarkup are optional.
 563// File should be either a string, FileBytes, or FileReader.
 564func (bot *BotAPI) SendDocument(config DocumentConfig) (Message, error) {
 565	if config.UseExistingDocument {
 566		v := url.Values{}
 567		v.Add("chat_id", strconv.Itoa(config.ChatID))
 568		v.Add("document", config.FileID)
 569		if config.ReplyToMessageID != 0 {
 570			v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
 571		}
 572		if config.ReplyMarkup != nil {
 573			data, err := json.Marshal(config.ReplyMarkup)
 574			if err != nil {
 575				return Message{}, err
 576			}
 577
 578			v.Add("reply_markup", string(data))
 579		}
 580
 581		resp, err := bot.MakeRequest("sendDocument", v)
 582		if err != nil {
 583			return Message{}, err
 584		}
 585
 586		var message Message
 587		json.Unmarshal(resp.Result, &message)
 588
 589		if bot.Debug {
 590			log.Printf("sendDocument req : %+v\n", v)
 591			log.Printf("sendDocument resp: %+v\n", message)
 592		}
 593
 594		return message, nil
 595	}
 596
 597	params := make(map[string]string)
 598
 599	params["chat_id"] = strconv.Itoa(config.ChatID)
 600	if config.ReplyToMessageID != 0 {
 601		params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
 602	}
 603	if config.ReplyMarkup != nil {
 604		data, err := json.Marshal(config.ReplyMarkup)
 605		if err != nil {
 606			return Message{}, err
 607		}
 608
 609		params["reply_markup"] = string(data)
 610	}
 611
 612	var file interface{}
 613	if config.FilePath == "" {
 614		file = config.File
 615	} else {
 616		file = config.FilePath
 617	}
 618
 619	resp, err := bot.UploadFile("sendDocument", params, "document", file)
 620	if err != nil {
 621		return Message{}, err
 622	}
 623
 624	var message Message
 625	json.Unmarshal(resp.Result, &message)
 626
 627	if bot.Debug {
 628		log.Printf("sendDocument resp: %+v\n", message)
 629	}
 630
 631	return message, nil
 632}
 633
 634// SendVoice sends or uploads a playable voice to a chat.
 635// If using a file, the file must be encoded as an .ogg with OPUS.
 636// You may use the tgutils.EncodeAudio func to assist you with this, if needed.
 637//
 638// Requires ChatID and FileID OR File.
 639// ReplyToMessageID and ReplyMarkup are optional.
 640// File should be either a string, FileBytes, or FileReader.
 641func (bot *BotAPI) SendVoice(config VoiceConfig) (Message, error) {
 642	if config.UseExistingVoice {
 643		v := url.Values{}
 644		v.Add("chat_id", strconv.Itoa(config.ChatID))
 645		v.Add("voice", config.FileID)
 646		if config.ReplyToMessageID != 0 {
 647			v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
 648		}
 649		if config.Duration != 0 {
 650			v.Add("duration", strconv.Itoa(config.Duration))
 651		}
 652		if config.ReplyMarkup != nil {
 653			data, err := json.Marshal(config.ReplyMarkup)
 654			if err != nil {
 655				return Message{}, err
 656			}
 657
 658			v.Add("reply_markup", string(data))
 659		}
 660
 661		resp, err := bot.MakeRequest("sendVoice", v)
 662		if err != nil {
 663			return Message{}, err
 664		}
 665
 666		var message Message
 667		json.Unmarshal(resp.Result, &message)
 668
 669		if bot.Debug {
 670			log.Printf("SendVoice req : %+v\n", v)
 671			log.Printf("SendVoice resp: %+v\n", message)
 672		}
 673
 674		return message, nil
 675	}
 676
 677	params := make(map[string]string)
 678
 679	params["chat_id"] = strconv.Itoa(config.ChatID)
 680	if config.ReplyToMessageID != 0 {
 681		params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
 682	}
 683	if config.Duration != 0 {
 684		params["duration"] = strconv.Itoa(config.Duration)
 685	}
 686	if config.ReplyMarkup != nil {
 687		data, err := json.Marshal(config.ReplyMarkup)
 688		if err != nil {
 689			return Message{}, err
 690		}
 691
 692		params["reply_markup"] = string(data)
 693	}
 694
 695	var file interface{}
 696	if config.FilePath == "" {
 697		file = config.File
 698	} else {
 699		file = config.FilePath
 700	}
 701
 702	resp, err := bot.UploadFile("SendVoice", params, "voice", file)
 703	if err != nil {
 704		return Message{}, err
 705	}
 706
 707	var message Message
 708	json.Unmarshal(resp.Result, &message)
 709
 710	if bot.Debug {
 711		log.Printf("SendVoice resp: %+v\n", message)
 712	}
 713
 714	return message, nil
 715}
 716
 717// SendSticker sends or uploads a sticker to a chat.
 718//
 719// Requires ChatID and FileID OR File.
 720// ReplyToMessageID and ReplyMarkup are optional.
 721// File should be either a string, FileBytes, or FileReader.
 722func (bot *BotAPI) SendSticker(config StickerConfig) (Message, error) {
 723	if config.UseExistingSticker {
 724		v := url.Values{}
 725		v.Add("chat_id", strconv.Itoa(config.ChatID))
 726		v.Add("sticker", config.FileID)
 727		if config.ReplyToMessageID != 0 {
 728			v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
 729		}
 730		if config.ReplyMarkup != nil {
 731			data, err := json.Marshal(config.ReplyMarkup)
 732			if err != nil {
 733				return Message{}, err
 734			}
 735
 736			v.Add("reply_markup", string(data))
 737		}
 738
 739		resp, err := bot.MakeRequest("sendSticker", v)
 740		if err != nil {
 741			return Message{}, err
 742		}
 743
 744		var message Message
 745		json.Unmarshal(resp.Result, &message)
 746
 747		if bot.Debug {
 748			log.Printf("sendSticker req : %+v\n", v)
 749			log.Printf("sendSticker resp: %+v\n", message)
 750		}
 751
 752		return message, nil
 753	}
 754
 755	params := make(map[string]string)
 756
 757	params["chat_id"] = strconv.Itoa(config.ChatID)
 758	if config.ReplyToMessageID != 0 {
 759		params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
 760	}
 761	if config.ReplyMarkup != nil {
 762		data, err := json.Marshal(config.ReplyMarkup)
 763		if err != nil {
 764			return Message{}, err
 765		}
 766
 767		params["reply_markup"] = string(data)
 768	}
 769
 770	var file interface{}
 771	if config.FilePath == "" {
 772		file = config.File
 773	} else {
 774		file = config.FilePath
 775	}
 776
 777	resp, err := bot.UploadFile("sendSticker", params, "sticker", file)
 778	if err != nil {
 779		return Message{}, err
 780	}
 781
 782	var message Message
 783	json.Unmarshal(resp.Result, &message)
 784
 785	if bot.Debug {
 786		log.Printf("sendSticker resp: %+v\n", message)
 787	}
 788
 789	return message, nil
 790}
 791
 792// SendVideo sends or uploads a video to a chat.
 793//
 794// Requires ChatID and FileID OR File.
 795// ReplyToMessageID and ReplyMarkup are optional.
 796// File should be either a string, FileBytes, or FileReader.
 797func (bot *BotAPI) SendVideo(config VideoConfig) (Message, error) {
 798	if config.UseExistingVideo {
 799		v := url.Values{}
 800		v.Add("chat_id", strconv.Itoa(config.ChatID))
 801		v.Add("video", config.FileID)
 802		if config.ReplyToMessageID != 0 {
 803			v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
 804		}
 805		if config.Duration != 0 {
 806			v.Add("duration", strconv.Itoa(config.Duration))
 807		}
 808		if config.Caption != "" {
 809			v.Add("caption", config.Caption)
 810		}
 811		if config.ReplyMarkup != nil {
 812			data, err := json.Marshal(config.ReplyMarkup)
 813			if err != nil {
 814				return Message{}, err
 815			}
 816
 817			v.Add("reply_markup", string(data))
 818		}
 819
 820		resp, err := bot.MakeRequest("sendVideo", v)
 821		if err != nil {
 822			return Message{}, err
 823		}
 824
 825		var message Message
 826		json.Unmarshal(resp.Result, &message)
 827
 828		if bot.Debug {
 829			log.Printf("sendVideo req : %+v\n", v)
 830			log.Printf("sendVideo resp: %+v\n", message)
 831		}
 832
 833		return message, nil
 834	}
 835
 836	params := make(map[string]string)
 837
 838	params["chat_id"] = strconv.Itoa(config.ChatID)
 839	if config.ReplyToMessageID != 0 {
 840		params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
 841	}
 842	if config.ReplyMarkup != nil {
 843		data, err := json.Marshal(config.ReplyMarkup)
 844		if err != nil {
 845			return Message{}, err
 846		}
 847
 848		params["reply_markup"] = string(data)
 849	}
 850
 851	var file interface{}
 852	if config.FilePath == "" {
 853		file = config.File
 854	} else {
 855		file = config.FilePath
 856	}
 857
 858	resp, err := bot.UploadFile("sendVideo", params, "video", file)
 859	if err != nil {
 860		return Message{}, err
 861	}
 862
 863	var message Message
 864	json.Unmarshal(resp.Result, &message)
 865
 866	if bot.Debug {
 867		log.Printf("sendVideo resp: %+v\n", message)
 868	}
 869
 870	return message, nil
 871}
 872
 873// SendLocation sends a location to a chat.
 874//
 875// Requires ChatID, Latitude, and Longitude.
 876// ReplyToMessageID and ReplyMarkup are optional.
 877func (bot *BotAPI) SendLocation(config LocationConfig) (Message, error) {
 878	v := url.Values{}
 879	v.Add("chat_id", strconv.Itoa(config.ChatID))
 880	v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64))
 881	v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64))
 882	if config.ReplyToMessageID != 0 {
 883		v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
 884	}
 885	if config.ReplyMarkup != nil {
 886		data, err := json.Marshal(config.ReplyMarkup)
 887		if err != nil {
 888			return Message{}, err
 889		}
 890
 891		v.Add("reply_markup", string(data))
 892	}
 893
 894	resp, err := bot.MakeRequest("sendLocation", v)
 895	if err != nil {
 896		return Message{}, err
 897	}
 898
 899	var message Message
 900	json.Unmarshal(resp.Result, &message)
 901
 902	if bot.Debug {
 903		log.Printf("sendLocation req : %+v\n", v)
 904		log.Printf("sendLocation resp: %+v\n", message)
 905	}
 906
 907	return message, nil
 908}
 909
 910// SendChatAction sets a current action in a chat.
 911//
 912// Requires ChatID and a valid Action (see Chat constants).
 913func (bot *BotAPI) SendChatAction(config ChatActionConfig) error {
 914	v := url.Values{}
 915	v.Add("chat_id", strconv.Itoa(config.ChatID))
 916	v.Add("action", config.Action)
 917
 918	_, err := bot.MakeRequest("sendChatAction", v)
 919	if err != nil {
 920		return err
 921	}
 922
 923	return nil
 924}
 925
 926// GetUserProfilePhotos gets a user's profile photos.
 927//
 928// Requires UserID.
 929// Offset and Limit are optional.
 930func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) {
 931	v := url.Values{}
 932	v.Add("user_id", strconv.Itoa(config.UserID))
 933	if config.Offset != 0 {
 934		v.Add("offset", strconv.Itoa(config.Offset))
 935	}
 936	if config.Limit != 0 {
 937		v.Add("limit", strconv.Itoa(config.Limit))
 938	}
 939
 940	resp, err := bot.MakeRequest("getUserProfilePhotos", v)
 941	if err != nil {
 942		return UserProfilePhotos{}, err
 943	}
 944
 945	var profilePhotos UserProfilePhotos
 946	json.Unmarshal(resp.Result, &profilePhotos)
 947
 948	if bot.Debug {
 949		log.Printf("getUserProfilePhotos req : %+v\n", v)
 950		log.Printf("getUserProfilePhotos resp: %+v\n", profilePhotos)
 951	}
 952
 953	return profilePhotos, nil
 954}
 955
 956// GetFile returns a file_id required to download a file.
 957//
 958// Requires FileID.
 959func (bot *BotAPI) GetFile(config FileConfig) (File, error) {
 960	v := url.Values{}
 961	v.Add("file_id", config.FileID)
 962
 963	resp, err := bot.MakeRequest("getFile", v)
 964	if err != nil {
 965		return File{}, err
 966	}
 967
 968	var file File
 969	json.Unmarshal(resp.Result, &file)
 970
 971	if bot.Debug {
 972		log.Printf("getFile req : %+v\n", v)
 973		log.Printf("getFile resp: %+v\n", file)
 974	}
 975
 976	return file, nil
 977}
 978
 979// GetUpdates fetches updates.
 980// If a WebHook is set, this will not return any data!
 981//
 982// Offset, Limit, and Timeout are optional.
 983// To not get old items, set Offset to one higher than the previous item.
 984// Set Timeout to a large number to reduce requests and get responses instantly.
 985func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) {
 986	v := url.Values{}
 987	if config.Offset > 0 {
 988		v.Add("offset", strconv.Itoa(config.Offset))
 989	}
 990	if config.Limit > 0 {
 991		v.Add("limit", strconv.Itoa(config.Limit))
 992	}
 993	if config.Timeout > 0 {
 994		v.Add("timeout", strconv.Itoa(config.Timeout))
 995	}
 996
 997	resp, err := bot.MakeRequest("getUpdates", v)
 998	if err != nil {
 999		return []Update{}, err
1000	}
1001
1002	var updates []Update
1003	json.Unmarshal(resp.Result, &updates)
1004
1005	if bot.Debug {
1006		log.Printf("getUpdates: %+v\n", updates)
1007	}
1008
1009	return updates, nil
1010}
1011
1012// SetWebhook sets a webhook.
1013// If this is set, GetUpdates will not get any data!
1014//
1015// Requires Url OR to set Clear to true.
1016func (bot *BotAPI) SetWebhook(config WebhookConfig) (APIResponse, error) {
1017	if config.Certificate == nil {
1018		v := url.Values{}
1019		if !config.Clear {
1020			v.Add("url", config.URL.String())
1021		}
1022
1023		return bot.MakeRequest("setWebhook", v)
1024	}
1025
1026	params := make(map[string]string)
1027	params["url"] = config.URL.String()
1028
1029	resp, err := bot.UploadFile("setWebhook", params, "certificate", config.Certificate)
1030	if err != nil {
1031		return APIResponse{}, err
1032	}
1033
1034	var apiResp APIResponse
1035	json.Unmarshal(resp.Result, &apiResp)
1036
1037	if bot.Debug {
1038		log.Printf("setWebhook resp: %+v\n", apiResp)
1039	}
1040
1041	return apiResp, nil
1042}