all repos — telegram-bot-api @ 76460f8a5b8b89b816998eefbac35b80ec96a06f

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