all repos — telegram-bot-api @ 07072ebce73bea4d334e95680a9cfaa88d9e2257

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