all repos — telegram-bot-api @ 60337023c51a0362a2e5df9801dd7ea70ef83cdc

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