all repos — telegram-bot-api @ a1207f63917b5bdf32db96e717fcdcc6d63a3f05

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