all repos — telegram-bot-api @ b92bbece9f8dad977b986a5c9ac2b5302ab6ca00

Golang bindings for the Telegram Bot API

methods.go (view raw)

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