all repos — telegram-bot-api @ d06eead68daccd87bfdd6a10d289fd47cdeec987

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