all repos — telegram-bot-api @ 2024a55b8769e678719ba5dce8463cc7e1415e53

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