all repos — telegram-bot-api @ 13a8bd025c70a5f1d0700f41c13ea339e073d3d0

Golang bindings for the Telegram Bot API

bot.go (view raw)

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