all repos — telegram-bot-api @ c12c67addea5d27a01e97cc3cf0f2d055c2a6f93

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	"io"
  9	"io/ioutil"
 10	"log"
 11	"mime/multipart"
 12	"net/http"
 13	"net/url"
 14	"os"
 15	"strconv"
 16)
 17
 18// Telegram constants
 19const (
 20	// APIEndpoint is the endpoint for all API methods, with formatting for Sprintf
 21	APIEndpoint = "https://api.telegram.org/bot%s/%s"
 22)
 23
 24// Constant values for ChatActions
 25const (
 26	ChatTyping         = "typing"
 27	ChatUploadPhoto    = "upload_photo"
 28	ChatRecordVideo    = "record_video"
 29	ChatUploadVideo    = "upload_video"
 30	ChatRecordAudio    = "record_audio"
 31	ChatUploadAudio    = "upload_audio"
 32	ChatUploadDocument = "upload_document"
 33	ChatFindLocation   = "find_location"
 34)
 35
 36// API errors
 37const (
 38	// APIForbidden happens when a token is bad
 39	APIForbidden = "forbidden"
 40)
 41
 42// MessageConfig contains information about a SendMessage request.
 43type MessageConfig struct {
 44	ChatID                int
 45	Text                  string
 46	DisableWebPagePreview bool
 47	ReplyToMessageID      int
 48	ReplyMarkup           interface{}
 49}
 50
 51// ForwardConfig contains information about a ForwardMessage request.
 52type ForwardConfig struct {
 53	ChatID     int
 54	FromChatID int
 55	MessageID  int
 56}
 57
 58// PhotoConfig contains information about a SendPhoto request.
 59type PhotoConfig struct {
 60	ChatID           int
 61	Caption          string
 62	ReplyToMessageID int
 63	ReplyMarkup      interface{}
 64	UseExistingPhoto bool
 65	FilePath         string
 66	FileID           string
 67}
 68
 69// AudioConfig contains information about a SendAudio request.
 70type AudioConfig struct {
 71	ChatID           int
 72	ReplyToMessageID int
 73	ReplyMarkup      interface{}
 74	UseExistingAudio bool
 75	FilePath         string
 76	FileID           string
 77}
 78
 79// DocumentConfig contains information about a SendDocument request.
 80type DocumentConfig struct {
 81	ChatID              int
 82	ReplyToMessageID    int
 83	ReplyMarkup         interface{}
 84	UseExistingDocument bool
 85	FilePath            string
 86	FileID              string
 87}
 88
 89// StickerConfig contains information about a SendSticker request.
 90type StickerConfig struct {
 91	ChatID             int
 92	ReplyToMessageID   int
 93	ReplyMarkup        interface{}
 94	UseExistingSticker bool
 95	FilePath           string
 96	FileID             string
 97}
 98
 99// VideoConfig contains information about a SendVideo request.
100type VideoConfig struct {
101	ChatID           int
102	ReplyToMessageID int
103	ReplyMarkup      interface{}
104	UseExistingVideo bool
105	FilePath         string
106	FileID           string
107}
108
109// LocationConfig contains information about a SendLocation request.
110type LocationConfig struct {
111	ChatID           int
112	Latitude         float64
113	Longitude        float64
114	ReplyToMessageID int
115	ReplyMarkup      interface{}
116}
117
118// ChatActionConfig contains information about a SendChatAction request.
119type ChatActionConfig struct {
120	ChatID int
121	Action string
122}
123
124// UserProfilePhotosConfig contains information about a GetUserProfilePhotos request.
125type UserProfilePhotosConfig struct {
126	UserID int
127	Offset int
128	Limit  int
129}
130
131// UpdateConfig contains information about a GetUpdates request.
132type UpdateConfig struct {
133	Offset  int
134	Limit   int
135	Timeout int
136}
137
138// WebhookConfig contains information about a SetWebhook request.
139type WebhookConfig struct {
140	Clear bool
141	URL   *url.URL
142}
143
144// MakeRequest makes a request to a specific endpoint with our token.
145// All requests are POSTs because Telegram doesn't care, and it's easier.
146func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse, error) {
147	resp, err := bot.Client.PostForm(fmt.Sprintf(APIEndpoint, bot.Token, endpoint), params)
148	if err != nil {
149		return APIResponse{}, err
150	}
151	defer resp.Body.Close()
152
153	if resp.StatusCode == http.StatusForbidden {
154		return APIResponse{}, errors.New(APIForbidden)
155	}
156
157	bytes, err := ioutil.ReadAll(resp.Body)
158	if err != nil {
159		return APIResponse{}, err
160	}
161
162	if bot.Debug {
163		log.Println(endpoint, string(bytes))
164	}
165
166	var apiResp APIResponse
167	json.Unmarshal(bytes, &apiResp)
168
169	if !apiResp.Ok {
170		return APIResponse{}, errors.New(apiResp.Description)
171	}
172
173	return apiResp, nil
174}
175
176// UploadFile makes a request to the API with a file.
177//
178// Requires the parameter to hold the file not be in the params.
179func (bot *BotAPI) UploadFile(endpoint string, params map[string]string, fieldname string, filename string) (APIResponse, error) {
180	var b bytes.Buffer
181	w := multipart.NewWriter(&b)
182
183	f, err := os.Open(filename)
184	if err != nil {
185		return APIResponse{}, err
186	}
187
188	fw, err := w.CreateFormFile(fieldname, filename)
189	if err != nil {
190		return APIResponse{}, err
191	}
192
193	if _, err = io.Copy(fw, f); err != nil {
194		return APIResponse{}, err
195	}
196
197	for key, val := range params {
198		if fw, err = w.CreateFormField(key); err != nil {
199			return APIResponse{}, err
200		}
201
202		if _, err = fw.Write([]byte(val)); err != nil {
203			return APIResponse{}, err
204		}
205	}
206
207	w.Close()
208
209	req, err := http.NewRequest("POST", fmt.Sprintf(APIEndpoint, bot.Token, endpoint), &b)
210	if err != nil {
211		return APIResponse{}, err
212	}
213
214	req.Header.Set("Content-Type", w.FormDataContentType())
215
216	res, err := bot.Client.Do(req)
217	if err != nil {
218		return APIResponse{}, err
219	}
220
221	bytes, err := ioutil.ReadAll(res.Body)
222	if err != nil {
223		return APIResponse{}, err
224	}
225
226	if bot.Debug {
227		log.Println(string(bytes[:]))
228	}
229
230	var apiResp APIResponse
231	json.Unmarshal(bytes, &apiResp)
232
233	return apiResp, nil
234}
235
236// GetMe fetches the currently authenticated bot.
237//
238// There are no parameters for this method.
239func (bot *BotAPI) GetMe() (User, error) {
240	resp, err := bot.MakeRequest("getMe", nil)
241	if err != nil {
242		return User{}, err
243	}
244
245	var user User
246	json.Unmarshal(resp.Result, &user)
247
248	if bot.Debug {
249		log.Printf("getMe: %+v\n", user)
250	}
251
252	return user, nil
253}
254
255// SendMessage sends a Message to a chat.
256//
257// Requires ChatID and Text.
258// DisableWebPagePreview, ReplyToMessageID, and ReplyMarkup are optional.
259func (bot *BotAPI) SendMessage(config MessageConfig) (Message, error) {
260	v := url.Values{}
261	v.Add("chat_id", strconv.Itoa(config.ChatID))
262	v.Add("text", config.Text)
263	v.Add("disable_web_page_preview", strconv.FormatBool(config.DisableWebPagePreview))
264	if config.ReplyToMessageID != 0 {
265		v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
266	}
267	if config.ReplyMarkup != nil {
268		data, err := json.Marshal(config.ReplyMarkup)
269		if err != nil {
270			return Message{}, err
271		}
272
273		v.Add("reply_markup", string(data))
274	}
275
276	resp, err := bot.MakeRequest("SendMessage", v)
277	if err != nil {
278		return Message{}, err
279	}
280
281	var message Message
282	json.Unmarshal(resp.Result, &message)
283
284	if bot.Debug {
285		log.Printf("SendMessage req : %+v\n", v)
286		log.Printf("SendMessage resp: %+v\n", message)
287	}
288
289	return message, nil
290}
291
292// ForwardMessage forwards a message from one chat to another.
293//
294// Requires ChatID (destination), FromChatID (source), and MessageID.
295func (bot *BotAPI) ForwardMessage(config ForwardConfig) (Message, error) {
296	v := url.Values{}
297	v.Add("chat_id", strconv.Itoa(config.ChatID))
298	v.Add("from_chat_id", strconv.Itoa(config.FromChatID))
299	v.Add("message_id", strconv.Itoa(config.MessageID))
300
301	resp, err := bot.MakeRequest("forwardMessage", v)
302	if err != nil {
303		return Message{}, err
304	}
305
306	var message Message
307	json.Unmarshal(resp.Result, &message)
308
309	if bot.Debug {
310		log.Printf("forwardMessage req : %+v\n", v)
311		log.Printf("forwardMessage resp: %+v\n", message)
312	}
313
314	return message, nil
315}
316
317// SendPhoto sends or uploads a photo to a chat.
318//
319// Requires ChatID and FileID OR FilePath.
320// Caption, ReplyToMessageID, and ReplyMarkup are optional.
321func (bot *BotAPI) SendPhoto(config PhotoConfig) (Message, error) {
322	if config.UseExistingPhoto {
323		v := url.Values{}
324		v.Add("chat_id", strconv.Itoa(config.ChatID))
325		v.Add("photo", config.FileID)
326		if config.Caption != "" {
327			v.Add("caption", config.Caption)
328		}
329		if config.ReplyToMessageID != 0 {
330			v.Add("reply_to_message_id", strconv.Itoa(config.ChatID))
331		}
332		if config.ReplyMarkup != nil {
333			data, err := json.Marshal(config.ReplyMarkup)
334			if err != nil {
335				return Message{}, err
336			}
337
338			v.Add("reply_markup", string(data))
339		}
340
341		resp, err := bot.MakeRequest("SendPhoto", v)
342		if err != nil {
343			return Message{}, err
344		}
345
346		var message Message
347		json.Unmarshal(resp.Result, &message)
348
349		if bot.Debug {
350			log.Printf("SendPhoto req : %+v\n", v)
351			log.Printf("SendPhoto resp: %+v\n", message)
352		}
353
354		return message, nil
355	}
356
357	params := make(map[string]string)
358	params["chat_id"] = strconv.Itoa(config.ChatID)
359	if config.Caption != "" {
360		params["caption"] = config.Caption
361	}
362	if config.ReplyToMessageID != 0 {
363		params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
364	}
365	if config.ReplyMarkup != nil {
366		data, err := json.Marshal(config.ReplyMarkup)
367		if err != nil {
368			return Message{}, err
369		}
370
371		params["reply_markup"] = string(data)
372	}
373
374	resp, err := bot.UploadFile("SendPhoto", params, "photo", config.FilePath)
375	if err != nil {
376		return Message{}, err
377	}
378
379	var message Message
380	json.Unmarshal(resp.Result, &message)
381
382	if bot.Debug {
383		log.Printf("SendPhoto resp: %+v\n", message)
384	}
385
386	return message, nil
387}
388
389// SendAudio sends or uploads an audio clip to a chat.
390// If using a file, the file must be encoded as an .ogg with OPUS.
391//
392// Requires ChatID and FileID OR FilePath.
393// ReplyToMessageID and ReplyMarkup are optional.
394func (bot *BotAPI) SendAudio(config AudioConfig) (Message, error) {
395	if config.UseExistingAudio {
396		v := url.Values{}
397		v.Add("chat_id", strconv.Itoa(config.ChatID))
398		v.Add("audio", config.FileID)
399		if config.ReplyToMessageID != 0 {
400			v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
401		}
402		if config.ReplyMarkup != nil {
403			data, err := json.Marshal(config.ReplyMarkup)
404			if err != nil {
405				return Message{}, err
406			}
407
408			v.Add("reply_markup", string(data))
409		}
410
411		resp, err := bot.MakeRequest("sendAudio", v)
412		if err != nil {
413			return Message{}, err
414		}
415
416		var message Message
417		json.Unmarshal(resp.Result, &message)
418
419		if bot.Debug {
420			log.Printf("sendAudio req : %+v\n", v)
421			log.Printf("sendAudio resp: %+v\n", message)
422		}
423
424		return message, nil
425	}
426
427	params := make(map[string]string)
428
429	params["chat_id"] = strconv.Itoa(config.ChatID)
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	resp, err := bot.UploadFile("sendAudio", params, "audio", config.FilePath)
443	if err != nil {
444		return Message{}, err
445	}
446
447	var message Message
448	json.Unmarshal(resp.Result, &message)
449
450	if bot.Debug {
451		log.Printf("sendAudio resp: %+v\n", message)
452	}
453
454	return message, nil
455}
456
457// SendDocument sends or uploads a document to a chat.
458//
459// Requires ChatID and FileID OR FilePath.
460// ReplyToMessageID and ReplyMarkup are optional.
461func (bot *BotAPI) SendDocument(config DocumentConfig) (Message, error) {
462	if config.UseExistingDocument {
463		v := url.Values{}
464		v.Add("chat_id", strconv.Itoa(config.ChatID))
465		v.Add("document", config.FileID)
466		if config.ReplyToMessageID != 0 {
467			v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
468		}
469		if config.ReplyMarkup != nil {
470			data, err := json.Marshal(config.ReplyMarkup)
471			if err != nil {
472				return Message{}, err
473			}
474
475			v.Add("reply_markup", string(data))
476		}
477
478		resp, err := bot.MakeRequest("sendDocument", v)
479		if err != nil {
480			return Message{}, err
481		}
482
483		var message Message
484		json.Unmarshal(resp.Result, &message)
485
486		if bot.Debug {
487			log.Printf("sendDocument req : %+v\n", v)
488			log.Printf("sendDocument resp: %+v\n", message)
489		}
490
491		return message, nil
492	}
493
494	params := make(map[string]string)
495
496	params["chat_id"] = strconv.Itoa(config.ChatID)
497	if config.ReplyToMessageID != 0 {
498		params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
499	}
500	if config.ReplyMarkup != nil {
501		data, err := json.Marshal(config.ReplyMarkup)
502		if err != nil {
503			return Message{}, err
504		}
505
506		params["reply_markup"] = string(data)
507	}
508
509	resp, err := bot.UploadFile("sendDocument", params, "document", config.FilePath)
510	if err != nil {
511		return Message{}, err
512	}
513
514	var message Message
515	json.Unmarshal(resp.Result, &message)
516
517	if bot.Debug {
518		log.Printf("sendDocument resp: %+v\n", message)
519	}
520
521	return message, nil
522}
523
524// SendSticker sends or uploads a sticker to a chat.
525//
526// Requires ChatID and FileID OR FilePath.
527// ReplyToMessageID and ReplyMarkup are optional.
528func (bot *BotAPI) SendSticker(config StickerConfig) (Message, error) {
529	if config.UseExistingSticker {
530		v := url.Values{}
531		v.Add("chat_id", strconv.Itoa(config.ChatID))
532		v.Add("sticker", config.FileID)
533		if config.ReplyToMessageID != 0 {
534			v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
535		}
536		if config.ReplyMarkup != nil {
537			data, err := json.Marshal(config.ReplyMarkup)
538			if err != nil {
539				return Message{}, err
540			}
541
542			v.Add("reply_markup", string(data))
543		}
544
545		resp, err := bot.MakeRequest("sendSticker", v)
546		if err != nil {
547			return Message{}, err
548		}
549
550		var message Message
551		json.Unmarshal(resp.Result, &message)
552
553		if bot.Debug {
554			log.Printf("sendSticker req : %+v\n", v)
555			log.Printf("sendSticker resp: %+v\n", message)
556		}
557
558		return message, nil
559	}
560
561	params := make(map[string]string)
562
563	params["chat_id"] = strconv.Itoa(config.ChatID)
564	if config.ReplyToMessageID != 0 {
565		params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
566	}
567	if config.ReplyMarkup != nil {
568		data, err := json.Marshal(config.ReplyMarkup)
569		if err != nil {
570			return Message{}, err
571		}
572
573		params["reply_markup"] = string(data)
574	}
575
576	resp, err := bot.UploadFile("sendSticker", params, "sticker", config.FilePath)
577	if err != nil {
578		return Message{}, err
579	}
580
581	var message Message
582	json.Unmarshal(resp.Result, &message)
583
584	if bot.Debug {
585		log.Printf("sendSticker resp: %+v\n", message)
586	}
587
588	return message, nil
589}
590
591// SendVideo sends or uploads a video to a chat.
592//
593// Requires ChatID and FileID OR FilePath.
594// ReplyToMessageID and ReplyMarkup are optional.
595func (bot *BotAPI) SendVideo(config VideoConfig) (Message, error) {
596	if config.UseExistingVideo {
597		v := url.Values{}
598		v.Add("chat_id", strconv.Itoa(config.ChatID))
599		v.Add("video", config.FileID)
600		if config.ReplyToMessageID != 0 {
601			v.Add("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			v.Add("reply_markup", string(data))
610		}
611
612		resp, err := bot.MakeRequest("sendVideo", v)
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("sendVideo req : %+v\n", v)
622			log.Printf("sendVideo resp: %+v\n", message)
623		}
624
625		return message, nil
626	}
627
628	params := make(map[string]string)
629
630	params["chat_id"] = strconv.Itoa(config.ChatID)
631	if config.ReplyToMessageID != 0 {
632		params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
633	}
634	if config.ReplyMarkup != nil {
635		data, err := json.Marshal(config.ReplyMarkup)
636		if err != nil {
637			return Message{}, err
638		}
639
640		params["reply_markup"] = string(data)
641	}
642
643	resp, err := bot.UploadFile("sendVideo", params, "video", config.FilePath)
644	if err != nil {
645		return Message{}, err
646	}
647
648	var message Message
649	json.Unmarshal(resp.Result, &message)
650
651	if bot.Debug {
652		log.Printf("sendVideo resp: %+v\n", message)
653	}
654
655	return message, nil
656}
657
658// SendLocation sends a location to a chat.
659//
660// Requires ChatID, Latitude, and Longitude.
661// ReplyToMessageID and ReplyMarkup are optional.
662func (bot *BotAPI) SendLocation(config LocationConfig) (Message, error) {
663	v := url.Values{}
664	v.Add("chat_id", strconv.Itoa(config.ChatID))
665	v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64))
666	v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64))
667	if config.ReplyToMessageID != 0 {
668		v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
669	}
670	if config.ReplyMarkup != nil {
671		data, err := json.Marshal(config.ReplyMarkup)
672		if err != nil {
673			return Message{}, err
674		}
675
676		v.Add("reply_markup", string(data))
677	}
678
679	resp, err := bot.MakeRequest("sendLocation", v)
680	if err != nil {
681		return Message{}, err
682	}
683
684	var message Message
685	json.Unmarshal(resp.Result, &message)
686
687	if bot.Debug {
688		log.Printf("sendLocation req : %+v\n", v)
689		log.Printf("sendLocation resp: %+v\n", message)
690	}
691
692	return message, nil
693}
694
695// SendChatAction sets a current action in a chat.
696//
697// Requires ChatID and a valid Action (see Chat constants).
698func (bot *BotAPI) SendChatAction(config ChatActionConfig) error {
699	v := url.Values{}
700	v.Add("chat_id", strconv.Itoa(config.ChatID))
701	v.Add("action", config.Action)
702
703	_, err := bot.MakeRequest("sendChatAction", v)
704	if err != nil {
705		return err
706	}
707
708	return nil
709}
710
711// GetUserProfilePhotos gets a user's profile photos.
712//
713// Requires UserID.
714// Offset and Limit are optional.
715func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) {
716	v := url.Values{}
717	v.Add("user_id", strconv.Itoa(config.UserID))
718	if config.Offset != 0 {
719		v.Add("offset", strconv.Itoa(config.Offset))
720	}
721	if config.Limit != 0 {
722		v.Add("limit", strconv.Itoa(config.Limit))
723	}
724
725	resp, err := bot.MakeRequest("getUserProfilePhotos", v)
726	if err != nil {
727		return UserProfilePhotos{}, err
728	}
729
730	var profilePhotos UserProfilePhotos
731	json.Unmarshal(resp.Result, &profilePhotos)
732
733	if bot.Debug {
734		log.Printf("getUserProfilePhotos req : %+v\n", v)
735		log.Printf("getUserProfilePhotos resp: %+v\n", profilePhotos)
736	}
737
738	return profilePhotos, nil
739}
740
741// GetUpdates fetches updates.
742// If a WebHook is set, this will not return any data!
743//
744// Offset, Limit, and Timeout are optional.
745// To not get old items, set Offset to one higher than the previous item.
746// Set Timeout to a large number to reduce requests and get responses instantly.
747func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) {
748	v := url.Values{}
749	if config.Offset > 0 {
750		v.Add("offset", strconv.Itoa(config.Offset))
751	}
752	if config.Limit > 0 {
753		v.Add("limit", strconv.Itoa(config.Limit))
754	}
755	if config.Timeout > 0 {
756		v.Add("timeout", strconv.Itoa(config.Timeout))
757	}
758
759	resp, err := bot.MakeRequest("getUpdates", v)
760	if err != nil {
761		return []Update{}, err
762	}
763
764	var updates []Update
765	json.Unmarshal(resp.Result, &updates)
766
767	if bot.Debug {
768		log.Printf("getUpdates: %+v\n", updates)
769	}
770
771	return updates, nil
772}
773
774// SetWebhook sets a webhook.
775// If this is set, GetUpdates will not get any data!
776//
777// Requires Url OR to set Clear to true.
778func (bot *BotAPI) SetWebhook(config WebhookConfig) error {
779	v := url.Values{}
780	if !config.Clear {
781		v.Add("url", config.URL.String())
782	}
783
784	_, err := bot.MakeRequest("setWebhook", v)
785
786	return err
787}