all repos — telegram-bot-api @ 5e5de566ee3e664ae54f033dc6b56dcf55b58cef

Golang bindings for the Telegram Bot API

methods.go (view raw)

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