all repos — telegram-bot-api @ d54197d756fb2036833c1d50c63a9be81a66c79b

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// You may use the tgutils.EncodeAudio func to assist you with this, if needed.
379//
380// Requires ChatID and FileID OR FilePath.
381// ReplyToMessageID and ReplyMarkup are optional.
382func (bot *BotAPI) SendAudio(config AudioConfig) (Message, error) {
383	if config.UseExistingAudio {
384		v := url.Values{}
385		v.Add("chat_id", strconv.Itoa(config.ChatID))
386		v.Add("audio", config.FileID)
387		if config.ReplyToMessageID != 0 {
388			v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
389		}
390		if config.Duration != 0 {
391			v.Add("duration", strconv.Itoa(config.Duration))
392		}
393		if config.ReplyMarkup != nil {
394			data, err := json.Marshal(config.ReplyMarkup)
395			if err != nil {
396				return Message{}, err
397			}
398
399			v.Add("reply_markup", string(data))
400		}
401
402		resp, err := bot.MakeRequest("sendAudio", v)
403		if err != nil {
404			return Message{}, err
405		}
406
407		var message Message
408		json.Unmarshal(resp.Result, &message)
409
410		if bot.Debug {
411			log.Printf("sendAudio req : %+v\n", v)
412			log.Printf("sendAudio resp: %+v\n", message)
413		}
414
415		return message, nil
416	}
417
418	params := make(map[string]string)
419
420	params["chat_id"] = strconv.Itoa(config.ChatID)
421	if config.ReplyToMessageID != 0 {
422		params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
423	}
424	if config.ReplyMarkup != nil {
425		data, err := json.Marshal(config.ReplyMarkup)
426		if err != nil {
427			return Message{}, err
428		}
429
430		params["reply_markup"] = string(data)
431	}
432
433	resp, err := bot.UploadFile("sendAudio", params, "audio", config.FilePath)
434	if err != nil {
435		return Message{}, err
436	}
437
438	var message Message
439	json.Unmarshal(resp.Result, &message)
440
441	if bot.Debug {
442		log.Printf("sendAudio resp: %+v\n", message)
443	}
444
445	return message, nil
446}
447
448// SendDocument sends or uploads a document to a chat.
449//
450// Requires ChatID and FileID OR FilePath.
451// ReplyToMessageID and ReplyMarkup are optional.
452func (bot *BotAPI) SendDocument(config DocumentConfig) (Message, error) {
453	if config.UseExistingDocument {
454		v := url.Values{}
455		v.Add("chat_id", strconv.Itoa(config.ChatID))
456		v.Add("document", config.FileID)
457		if config.ReplyToMessageID != 0 {
458			v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
459		}
460		if config.ReplyMarkup != nil {
461			data, err := json.Marshal(config.ReplyMarkup)
462			if err != nil {
463				return Message{}, err
464			}
465
466			v.Add("reply_markup", string(data))
467		}
468
469		resp, err := bot.MakeRequest("sendDocument", v)
470		if err != nil {
471			return Message{}, err
472		}
473
474		var message Message
475		json.Unmarshal(resp.Result, &message)
476
477		if bot.Debug {
478			log.Printf("sendDocument req : %+v\n", v)
479			log.Printf("sendDocument resp: %+v\n", message)
480		}
481
482		return message, nil
483	}
484
485	params := make(map[string]string)
486
487	params["chat_id"] = strconv.Itoa(config.ChatID)
488	if config.ReplyToMessageID != 0 {
489		params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
490	}
491	if config.ReplyMarkup != nil {
492		data, err := json.Marshal(config.ReplyMarkup)
493		if err != nil {
494			return Message{}, err
495		}
496
497		params["reply_markup"] = string(data)
498	}
499
500	resp, err := bot.UploadFile("sendDocument", params, "document", config.FilePath)
501	if err != nil {
502		return Message{}, err
503	}
504
505	var message Message
506	json.Unmarshal(resp.Result, &message)
507
508	if bot.Debug {
509		log.Printf("sendDocument resp: %+v\n", message)
510	}
511
512	return message, nil
513}
514
515// SendSticker sends or uploads a sticker to a chat.
516//
517// Requires ChatID and FileID OR FilePath.
518// ReplyToMessageID and ReplyMarkup are optional.
519func (bot *BotAPI) SendSticker(config StickerConfig) (Message, error) {
520	if config.UseExistingSticker {
521		v := url.Values{}
522		v.Add("chat_id", strconv.Itoa(config.ChatID))
523		v.Add("sticker", config.FileID)
524		if config.ReplyToMessageID != 0 {
525			v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
526		}
527		if config.ReplyMarkup != nil {
528			data, err := json.Marshal(config.ReplyMarkup)
529			if err != nil {
530				return Message{}, err
531			}
532
533			v.Add("reply_markup", string(data))
534		}
535
536		resp, err := bot.MakeRequest("sendSticker", v)
537		if err != nil {
538			return Message{}, err
539		}
540
541		var message Message
542		json.Unmarshal(resp.Result, &message)
543
544		if bot.Debug {
545			log.Printf("sendSticker req : %+v\n", v)
546			log.Printf("sendSticker resp: %+v\n", message)
547		}
548
549		return message, nil
550	}
551
552	params := make(map[string]string)
553
554	params["chat_id"] = strconv.Itoa(config.ChatID)
555	if config.ReplyToMessageID != 0 {
556		params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
557	}
558	if config.ReplyMarkup != nil {
559		data, err := json.Marshal(config.ReplyMarkup)
560		if err != nil {
561			return Message{}, err
562		}
563
564		params["reply_markup"] = string(data)
565	}
566
567	resp, err := bot.UploadFile("sendSticker", params, "sticker", config.FilePath)
568	if err != nil {
569		return Message{}, err
570	}
571
572	var message Message
573	json.Unmarshal(resp.Result, &message)
574
575	if bot.Debug {
576		log.Printf("sendSticker resp: %+v\n", message)
577	}
578
579	return message, nil
580}
581
582// SendVideo sends or uploads a video to a chat.
583//
584// Requires ChatID and FileID OR FilePath.
585// ReplyToMessageID and ReplyMarkup are optional.
586func (bot *BotAPI) SendVideo(config VideoConfig) (Message, error) {
587	if config.UseExistingVideo {
588		v := url.Values{}
589		v.Add("chat_id", strconv.Itoa(config.ChatID))
590		v.Add("video", config.FileID)
591		if config.ReplyToMessageID != 0 {
592			v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
593		}
594		if config.Duration != 0 {
595			v.Add("duration", strconv.Itoa(config.Duration))
596		}
597		if config.Caption != "" {
598			v.Add("caption", config.Caption)
599		}
600		if config.ReplyMarkup != nil {
601			data, err := json.Marshal(config.ReplyMarkup)
602			if err != nil {
603				return Message{}, err
604			}
605
606			v.Add("reply_markup", string(data))
607		}
608
609		resp, err := bot.MakeRequest("sendVideo", v)
610		if err != nil {
611			return Message{}, err
612		}
613
614		var message Message
615		json.Unmarshal(resp.Result, &message)
616
617		if bot.Debug {
618			log.Printf("sendVideo req : %+v\n", v)
619			log.Printf("sendVideo resp: %+v\n", message)
620		}
621
622		return message, nil
623	}
624
625	params := make(map[string]string)
626
627	params["chat_id"] = strconv.Itoa(config.ChatID)
628	if config.ReplyToMessageID != 0 {
629		params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
630	}
631	if config.ReplyMarkup != nil {
632		data, err := json.Marshal(config.ReplyMarkup)
633		if err != nil {
634			return Message{}, err
635		}
636
637		params["reply_markup"] = string(data)
638	}
639
640	resp, err := bot.UploadFile("sendVideo", params, "video", config.FilePath)
641	if err != nil {
642		return Message{}, err
643	}
644
645	var message Message
646	json.Unmarshal(resp.Result, &message)
647
648	if bot.Debug {
649		log.Printf("sendVideo resp: %+v\n", message)
650	}
651
652	return message, nil
653}
654
655// SendLocation sends a location to a chat.
656//
657// Requires ChatID, Latitude, and Longitude.
658// ReplyToMessageID and ReplyMarkup are optional.
659func (bot *BotAPI) SendLocation(config LocationConfig) (Message, error) {
660	v := url.Values{}
661	v.Add("chat_id", strconv.Itoa(config.ChatID))
662	v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64))
663	v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64))
664	if config.ReplyToMessageID != 0 {
665		v.Add("reply_to_message_id", strconv.Itoa(config.ReplyToMessageID))
666	}
667	if config.ReplyMarkup != nil {
668		data, err := json.Marshal(config.ReplyMarkup)
669		if err != nil {
670			return Message{}, err
671		}
672
673		v.Add("reply_markup", string(data))
674	}
675
676	resp, err := bot.MakeRequest("sendLocation", v)
677	if err != nil {
678		return Message{}, err
679	}
680
681	var message Message
682	json.Unmarshal(resp.Result, &message)
683
684	if bot.Debug {
685		log.Printf("sendLocation req : %+v\n", v)
686		log.Printf("sendLocation resp: %+v\n", message)
687	}
688
689	return message, nil
690}
691
692// SendChatAction sets a current action in a chat.
693//
694// Requires ChatID and a valid Action (see Chat constants).
695func (bot *BotAPI) SendChatAction(config ChatActionConfig) error {
696	v := url.Values{}
697	v.Add("chat_id", strconv.Itoa(config.ChatID))
698	v.Add("action", config.Action)
699
700	_, err := bot.MakeRequest("sendChatAction", v)
701	if err != nil {
702		return err
703	}
704
705	return nil
706}
707
708// GetUserProfilePhotos gets a user's profile photos.
709//
710// Requires UserID.
711// Offset and Limit are optional.
712func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) {
713	v := url.Values{}
714	v.Add("user_id", strconv.Itoa(config.UserID))
715	if config.Offset != 0 {
716		v.Add("offset", strconv.Itoa(config.Offset))
717	}
718	if config.Limit != 0 {
719		v.Add("limit", strconv.Itoa(config.Limit))
720	}
721
722	resp, err := bot.MakeRequest("getUserProfilePhotos", v)
723	if err != nil {
724		return UserProfilePhotos{}, err
725	}
726
727	var profilePhotos UserProfilePhotos
728	json.Unmarshal(resp.Result, &profilePhotos)
729
730	if bot.Debug {
731		log.Printf("getUserProfilePhotos req : %+v\n", v)
732		log.Printf("getUserProfilePhotos resp: %+v\n", profilePhotos)
733	}
734
735	return profilePhotos, nil
736}
737
738// GetUpdates fetches updates.
739// If a WebHook is set, this will not return any data!
740//
741// Offset, Limit, and Timeout are optional.
742// To not get old items, set Offset to one higher than the previous item.
743// Set Timeout to a large number to reduce requests and get responses instantly.
744func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) {
745	v := url.Values{}
746	if config.Offset > 0 {
747		v.Add("offset", strconv.Itoa(config.Offset))
748	}
749	if config.Limit > 0 {
750		v.Add("limit", strconv.Itoa(config.Limit))
751	}
752	if config.Timeout > 0 {
753		v.Add("timeout", strconv.Itoa(config.Timeout))
754	}
755
756	resp, err := bot.MakeRequest("getUpdates", v)
757	if err != nil {
758		return []Update{}, err
759	}
760
761	var updates []Update
762	json.Unmarshal(resp.Result, &updates)
763
764	if bot.Debug {
765		log.Printf("getUpdates: %+v\n", updates)
766	}
767
768	return updates, nil
769}
770
771// SetWebhook sets a webhook.
772// If this is set, GetUpdates will not get any data!
773//
774// Requires Url OR to set Clear to true.
775func (bot *BotAPI) SetWebhook(config WebhookConfig) error {
776	v := url.Values{}
777	if !config.Clear {
778		v.Add("url", config.URL.String())
779	}
780
781	_, err := bot.MakeRequest("setWebhook", v)
782
783	return err
784}