all repos — telegram-bot-api @ 96a104444e0fd64c3a220b763abf57386a7d909e

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