all repos — telegram-bot-api @ 57a07c0c22adbfe3e8543582b35b73cbb6a80865

Golang bindings for the Telegram Bot API

bot.go (view raw)

  1// Package tgbotapi has bindings for interacting with the Telegram Bot API.
  2package tgbotapi
  3
  4import (
  5	"bytes"
  6	"encoding/json"
  7	"errors"
  8	"fmt"
  9	"github.com/technoweenie/multipartstreamer"
 10	"io/ioutil"
 11	"log"
 12	"net/http"
 13	"net/url"
 14	"os"
 15	"strconv"
 16	"time"
 17)
 18
 19// BotAPI has methods for interacting with all of Telegram's Bot API endpoints.
 20type BotAPI struct {
 21	Token   string       `json:"token"`
 22	Debug   bool         `json:"debug"`
 23	Self    User         `json:"-"`
 24	Updates chan Update  `json:"-"`
 25	Client  *http.Client `json:"-"`
 26}
 27
 28// NewBotAPI creates a new BotAPI instance.
 29// Requires a token, provided by @BotFather on Telegram
 30func NewBotAPI(token string) (*BotAPI, error) {
 31	return NewBotAPIWithClient(token, &http.Client{})
 32}
 33
 34// NewBotAPIWithClient creates a new BotAPI instance passing an http.Client.
 35// Requires a token, provided by @BotFather on Telegram
 36func NewBotAPIWithClient(token string, client *http.Client) (*BotAPI, error) {
 37	bot := &BotAPI{
 38		Token:  token,
 39		Client: client,
 40	}
 41
 42	self, err := bot.GetMe()
 43	if err != nil {
 44		return &BotAPI{}, err
 45	}
 46
 47	bot.Self = self
 48
 49	return bot, nil
 50}
 51
 52// MakeRequest makes a request to a specific endpoint with our token.
 53// All requests are POSTs because Telegram doesn't care, and it's easier.
 54func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse, error) {
 55	resp, err := bot.Client.PostForm(fmt.Sprintf(APIEndpoint, bot.Token, endpoint), params)
 56	if err != nil {
 57		return APIResponse{}, err
 58	}
 59	defer resp.Body.Close()
 60
 61	if resp.StatusCode == http.StatusForbidden {
 62		return APIResponse{}, errors.New(APIForbidden)
 63	}
 64
 65	bytes, err := ioutil.ReadAll(resp.Body)
 66	if err != nil {
 67		return APIResponse{}, err
 68	}
 69
 70	if bot.Debug {
 71		log.Println(endpoint, string(bytes))
 72	}
 73
 74	var apiResp APIResponse
 75	json.Unmarshal(bytes, &apiResp)
 76
 77	if !apiResp.Ok {
 78		return APIResponse{}, errors.New(apiResp.Description)
 79	}
 80
 81	return apiResp, nil
 82}
 83
 84// UploadFile makes a request to the API with a file.
 85//
 86// Requires the parameter to hold the file not be in the params.
 87// File should be a string to a file path, a FileBytes struct, or a FileReader struct.
 88func (bot *BotAPI) UploadFile(endpoint string, params map[string]string, fieldname string, file interface{}) (APIResponse, error) {
 89	ms := multipartstreamer.New()
 90	ms.WriteFields(params)
 91
 92	switch f := file.(type) {
 93	case string:
 94		fileHandle, err := os.Open(f)
 95		if err != nil {
 96			return APIResponse{}, err
 97		}
 98		defer fileHandle.Close()
 99
100		fi, err := os.Stat(f)
101		if err != nil {
102			return APIResponse{}, err
103		}
104
105		ms.WriteReader(fieldname, fileHandle.Name(), fi.Size(), fileHandle)
106	case FileBytes:
107		buf := bytes.NewBuffer(f.Bytes)
108		ms.WriteReader(fieldname, f.Name, int64(len(f.Bytes)), buf)
109	case FileReader:
110		if f.Size == -1 {
111			data, err := ioutil.ReadAll(f.Reader)
112			if err != nil {
113				return APIResponse{}, err
114			}
115			buf := bytes.NewBuffer(data)
116
117			ms.WriteReader(fieldname, f.Name, int64(len(data)), buf)
118
119			break
120		}
121
122		ms.WriteReader(fieldname, f.Name, f.Size, f.Reader)
123	default:
124		return APIResponse{}, errors.New("bad file type")
125	}
126
127	req, err := http.NewRequest("POST", fmt.Sprintf(APIEndpoint, bot.Token, endpoint), nil)
128	ms.SetupRequest(req)
129	if err != nil {
130		return APIResponse{}, err
131	}
132
133	res, err := bot.Client.Do(req)
134	if err != nil {
135		return APIResponse{}, err
136	}
137	defer res.Body.Close()
138
139	bytes, err := ioutil.ReadAll(res.Body)
140	if err != nil {
141		return APIResponse{}, err
142	}
143
144	if bot.Debug {
145		log.Println(string(bytes[:]))
146	}
147
148	var apiResp APIResponse
149	json.Unmarshal(bytes, &apiResp)
150
151	if !apiResp.Ok {
152		return APIResponse{}, errors.New(apiResp.Description)
153	}
154
155	return apiResp, nil
156}
157
158// GetMe fetches the currently authenticated bot.
159//
160// There are no parameters for this method.
161func (bot *BotAPI) GetMe() (User, error) {
162	resp, err := bot.MakeRequest("getMe", nil)
163	if err != nil {
164		return User{}, err
165	}
166
167	var user User
168	json.Unmarshal(resp.Result, &user)
169
170	if bot.Debug {
171		log.Printf("getMe: %+v\n", user)
172	}
173
174	return user, nil
175}
176
177func (bot *BotAPI) Send(c Chattable) error {
178	return nil
179}
180
181// SendMessage sends a Message to a chat.
182//
183// Requires ChatID and Text.
184// DisableWebPagePreview, ReplyToMessageID, and ReplyMarkup are optional.
185func (bot *BotAPI) SendMessage(config MessageConfig) (Message, error) {
186	v, err := config.Values()
187
188	if err != nil {
189		return Message{}, err
190	}
191
192	resp, err := bot.MakeRequest("SendMessage", v)
193	if err != nil {
194		return Message{}, err
195	}
196
197	var message Message
198	json.Unmarshal(resp.Result, &message)
199
200	if bot.Debug {
201		log.Printf("SendMessage req : %+v\n", v)
202		log.Printf("SendMessage resp: %+v\n", message)
203	}
204
205	return message, nil
206}
207
208// ForwardMessage forwards a message from one chat to another.
209//
210// Requires ChatID (destination), FromChatID (source), and MessageID.
211func (bot *BotAPI) ForwardMessage(config ForwardConfig) (Message, error) {
212	v, _:= config.Values()
213
214	resp, err := bot.MakeRequest("forwardMessage", v)
215	if err != nil {
216		return Message{}, err
217	}
218
219	var message Message
220	json.Unmarshal(resp.Result, &message)
221
222	if bot.Debug {
223		log.Printf("forwardMessage req : %+v\n", v)
224		log.Printf("forwardMessage resp: %+v\n", message)
225	}
226
227	return message, nil
228}
229
230// SendPhoto sends or uploads a photo to a chat.
231//
232// Requires ChatID and FileID OR File.
233// Caption, ReplyToMessageID, and ReplyMarkup are optional.
234// File should be either a string, FileBytes, or FileReader.
235func (bot *BotAPI) SendPhoto(config PhotoConfig) (Message, error) {
236	if config.UseExistingPhoto {
237		v, err := config.Values()
238
239		if err != nil {
240			return Message{}, err
241		}
242
243		resp, err := bot.MakeRequest("SendPhoto", v)
244		if err != nil {
245			return Message{}, err
246		}
247
248		var message Message
249		json.Unmarshal(resp.Result, &message)
250
251		if bot.Debug {
252			log.Printf("SendPhoto req : %+v\n", v)
253			log.Printf("SendPhoto resp: %+v\n", message)
254		}
255
256		return message, nil
257	}
258
259	params := make(map[string]string)
260	if config.ChannelUsername != "" {
261		params["chat_id"] = config.ChannelUsername
262	} else {
263		params["chat_id"] = strconv.Itoa(config.ChatID)
264	}
265	if config.Caption != "" {
266		params["caption"] = config.Caption
267	}
268	if config.ReplyToMessageID != 0 {
269		params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
270	}
271	if config.ReplyMarkup != nil {
272		data, err := json.Marshal(config.ReplyMarkup)
273		if err != nil {
274			return Message{}, err
275		}
276
277		params["reply_markup"] = string(data)
278	}
279
280	var file interface{}
281	if config.FilePath == "" {
282		file = config.File
283	} else {
284		file = config.FilePath
285	}
286
287	resp, err := bot.UploadFile("SendPhoto", params, "photo", file)
288	if err != nil {
289		return Message{}, err
290	}
291
292	var message Message
293	json.Unmarshal(resp.Result, &message)
294
295	if bot.Debug {
296		log.Printf("SendPhoto resp: %+v\n", message)
297	}
298
299	return message, nil
300}
301
302// SendAudio sends or uploads an audio clip to a chat.
303// If using a file, the file must be in the .mp3 format.
304//
305// When the fields title and performer are both empty and
306// the mime-type of the file to be sent is not audio/mpeg,
307// the file must be an .ogg file encoded with OPUS.
308// You may use the tgutils.EncodeAudio func to assist you with this, if needed.
309//
310// Requires ChatID and FileID OR File.
311// ReplyToMessageID and ReplyMarkup are optional.
312// File should be either a string, FileBytes, or FileReader.
313func (bot *BotAPI) SendAudio(config AudioConfig) (Message, error) {
314	if config.UseExistingAudio {
315		v, err := config.Values()
316		if err != nil {
317			return Message{}, err
318		}
319
320		resp, err := bot.MakeRequest("sendAudio", v)
321		if err != nil {
322			return Message{}, err
323		}
324
325		var message Message
326		json.Unmarshal(resp.Result, &message)
327
328		if bot.Debug {
329			log.Printf("sendAudio req : %+v\n", v)
330			log.Printf("sendAudio resp: %+v\n", message)
331		}
332
333		return message, nil
334	}
335
336	params := make(map[string]string)
337
338	if config.ChannelUsername != "" {
339		params["chat_id"] = config.ChannelUsername
340	} else {
341		params["chat_id"] = strconv.Itoa(config.ChatID)
342	}
343	if config.ReplyToMessageID != 0 {
344		params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
345	}
346	if config.Duration != 0 {
347		params["duration"] = strconv.Itoa(config.Duration)
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	if config.Performer != "" {
358		params["performer"] = config.Performer
359	}
360	if config.Title != "" {
361		params["title"] = config.Title
362	}
363
364	var file interface{}
365	if config.FilePath == "" {
366		file = config.File
367	} else {
368		file = config.FilePath
369	}
370
371	resp, err := bot.UploadFile("sendAudio", params, "audio", file)
372	if err != nil {
373		return Message{}, err
374	}
375
376	var message Message
377	json.Unmarshal(resp.Result, &message)
378
379	if bot.Debug {
380		log.Printf("sendAudio resp: %+v\n", message)
381	}
382
383	return message, nil
384}
385
386// SendDocument sends or uploads a document to a chat.
387//
388// Requires ChatID and FileID OR File.
389// ReplyToMessageID and ReplyMarkup are optional.
390// File should be either a string, FileBytes, or FileReader.
391func (bot *BotAPI) SendDocument(config DocumentConfig) (Message, error) {
392	if config.UseExistingDocument {
393		v, err := config.Values()
394		if err != nil {
395			return Message{}, err
396		}
397
398		resp, err := bot.MakeRequest("sendDocument", v)
399		if err != nil {
400			return Message{}, err
401		}
402
403		var message Message
404		json.Unmarshal(resp.Result, &message)
405
406		if bot.Debug {
407			log.Printf("sendDocument req : %+v\n", v)
408			log.Printf("sendDocument resp: %+v\n", message)
409		}
410
411		return message, nil
412	}
413
414	params := make(map[string]string)
415
416	if config.ChannelUsername != "" {
417		params["chat_id"] = config.ChannelUsername
418	} else {
419		params["chat_id"] = strconv.Itoa(config.ChatID)
420	}
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	var file interface{}
434	if config.FilePath == "" {
435		file = config.File
436	} else {
437		file = config.FilePath
438	}
439
440	resp, err := bot.UploadFile("sendDocument", params, "document", file)
441	if err != nil {
442		return Message{}, err
443	}
444
445	var message Message
446	json.Unmarshal(resp.Result, &message)
447
448	if bot.Debug {
449		log.Printf("sendDocument resp: %+v\n", message)
450	}
451
452	return message, nil
453}
454
455// SendVoice sends or uploads a playable voice to a chat.
456// If using a file, the file must be encoded as an .ogg with OPUS.
457// You may use the tgutils.EncodeAudio func to assist you with this, if needed.
458//
459// Requires ChatID and FileID OR File.
460// ReplyToMessageID and ReplyMarkup are optional.
461// File should be either a string, FileBytes, or FileReader.
462func (bot *BotAPI) SendVoice(config VoiceConfig) (Message, error) {
463	if config.UseExistingVoice {
464		v, err := config.Values()
465		if err != nil {
466			return Message{}, err
467		}
468
469		resp, err := bot.MakeRequest("sendVoice", 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("SendVoice req : %+v\n", v)
479			log.Printf("SendVoice resp: %+v\n", message)
480		}
481
482		return message, nil
483	}
484
485	params := make(map[string]string)
486
487	if config.ChannelUsername != "" {
488		params["chat_id"] = config.ChannelUsername
489	} else {
490		params["chat_id"] = strconv.Itoa(config.ChatID)
491	}
492	if config.ReplyToMessageID != 0 {
493		params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
494	}
495	if config.Duration != 0 {
496		params["duration"] = strconv.Itoa(config.Duration)
497	}
498	if config.ReplyMarkup != nil {
499		data, err := json.Marshal(config.ReplyMarkup)
500		if err != nil {
501			return Message{}, err
502		}
503
504		params["reply_markup"] = string(data)
505	}
506
507	var file interface{}
508	if config.FilePath == "" {
509		file = config.File
510	} else {
511		file = config.FilePath
512	}
513
514	resp, err := bot.UploadFile("SendVoice", params, "voice", file)
515	if err != nil {
516		return Message{}, err
517	}
518
519	var message Message
520	json.Unmarshal(resp.Result, &message)
521
522	if bot.Debug {
523		log.Printf("SendVoice resp: %+v\n", message)
524	}
525
526	return message, nil
527}
528
529// SendSticker sends or uploads a sticker to a chat.
530//
531// Requires ChatID and FileID OR File.
532// ReplyToMessageID and ReplyMarkup are optional.
533// File should be either a string, FileBytes, or FileReader.
534func (bot *BotAPI) SendSticker(config StickerConfig) (Message, error) {
535	if config.UseExistingSticker {
536		v, err := config.Values()
537		if err != nil {
538			return Message{}, err
539		}
540
541		resp, err := bot.MakeRequest("sendSticker", v)
542		if err != nil {
543			return Message{}, err
544		}
545
546		var message Message
547		json.Unmarshal(resp.Result, &message)
548
549		if bot.Debug {
550			log.Printf("sendSticker req : %+v\n", v)
551			log.Printf("sendSticker resp: %+v\n", message)
552		}
553
554		return message, nil
555	}
556
557	params := make(map[string]string)
558
559	if config.ChannelUsername != "" {
560		params["chat_id"] = config.ChannelUsername
561	} else {
562		params["chat_id"] = strconv.Itoa(config.ChatID)
563	}
564	if config.ReplyToMessageID != 0 {
565		params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
566	}
567	if config.ReplyMarkup != nil {
568		data, err := json.Marshal(config.ReplyMarkup)
569		if err != nil {
570			return Message{}, err
571		}
572
573		params["reply_markup"] = string(data)
574	}
575
576	var file interface{}
577	if config.FilePath == "" {
578		file = config.File
579	} else {
580		file = config.FilePath
581	}
582
583	resp, err := bot.UploadFile("sendSticker", params, "sticker", file)
584	if err != nil {
585		return Message{}, err
586	}
587
588	var message Message
589	json.Unmarshal(resp.Result, &message)
590
591	if bot.Debug {
592		log.Printf("sendSticker resp: %+v\n", message)
593	}
594
595	return message, nil
596}
597
598// SendVideo sends or uploads a video to a chat.
599//
600// Requires ChatID and FileID OR File.
601// ReplyToMessageID and ReplyMarkup are optional.
602// File should be either a string, FileBytes, or FileReader.
603func (bot *BotAPI) SendVideo(config VideoConfig) (Message, error) {
604	if config.UseExistingVideo {
605		v, err := config.Values()
606		if err != nil {
607			return Message{}, err
608		}
609
610		resp, err := bot.MakeRequest("sendVideo", v)
611		if err != nil {
612			return Message{}, err
613		}
614
615		var message Message
616		json.Unmarshal(resp.Result, &message)
617
618		if bot.Debug {
619			log.Printf("sendVideo req : %+v\n", v)
620			log.Printf("sendVideo resp: %+v\n", message)
621		}
622
623		return message, nil
624	}
625
626	params := make(map[string]string)
627
628	if config.ChannelUsername != "" {
629		params["chat_id"] = config.ChannelUsername
630	} else {
631		params["chat_id"] = strconv.Itoa(config.ChatID)
632	}
633	if config.ReplyToMessageID != 0 {
634		params["reply_to_message_id"] = strconv.Itoa(config.ReplyToMessageID)
635	}
636	if config.ReplyMarkup != nil {
637		data, err := json.Marshal(config.ReplyMarkup)
638		if err != nil {
639			return Message{}, err
640		}
641
642		params["reply_markup"] = string(data)
643	}
644
645	var file interface{}
646	if config.FilePath == "" {
647		file = config.File
648	} else {
649		file = config.FilePath
650	}
651
652	resp, err := bot.UploadFile("sendVideo", params, "video", file)
653	if err != nil {
654		return Message{}, err
655	}
656
657	var message Message
658	json.Unmarshal(resp.Result, &message)
659
660	if bot.Debug {
661		log.Printf("sendVideo resp: %+v\n", message)
662	}
663
664	return message, nil
665}
666
667// SendLocation sends a location to a chat.
668//
669// Requires ChatID, Latitude, and Longitude.
670// ReplyToMessageID and ReplyMarkup are optional.
671func (bot *BotAPI) SendLocation(config LocationConfig) (Message, error) {
672	v, err := config.Values()
673	if err != nil {
674		return Message{}, err
675	}
676
677	resp, err := bot.MakeRequest("sendLocation", v)
678	if err != nil {
679		return Message{}, err
680	}
681
682	var message Message
683	json.Unmarshal(resp.Result, &message)
684
685	if bot.Debug {
686		log.Printf("sendLocation req : %+v\n", v)
687		log.Printf("sendLocation resp: %+v\n", message)
688	}
689
690	return message, nil
691}
692
693// SendChatAction sets a current action in a chat.
694//
695// Requires ChatID and a valid Action (see Chat constants).
696func (bot *BotAPI) SendChatAction(config ChatActionConfig) error {
697	v, _ := config.Values()
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// GetFile returns a file_id required to download a file.
738//
739// Requires FileID.
740func (bot *BotAPI) GetFile(config FileConfig) (File, error) {
741	v := url.Values{}
742	v.Add("file_id", config.FileID)
743
744	resp, err := bot.MakeRequest("getFile", v)
745	if err != nil {
746		return File{}, err
747	}
748
749	var file File
750	json.Unmarshal(resp.Result, &file)
751
752	if bot.Debug {
753		log.Printf("getFile req : %+v\n", v)
754		log.Printf("getFile resp: %+v\n", file)
755	}
756
757	return file, nil
758}
759
760// GetUpdates fetches updates.
761// If a WebHook is set, this will not return any data!
762//
763// Offset, Limit, and Timeout are optional.
764// To not get old items, set Offset to one higher than the previous item.
765// Set Timeout to a large number to reduce requests and get responses instantly.
766func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) {
767	v := url.Values{}
768	if config.Offset > 0 {
769		v.Add("offset", strconv.Itoa(config.Offset))
770	}
771	if config.Limit > 0 {
772		v.Add("limit", strconv.Itoa(config.Limit))
773	}
774	if config.Timeout > 0 {
775		v.Add("timeout", strconv.Itoa(config.Timeout))
776	}
777
778	resp, err := bot.MakeRequest("getUpdates", v)
779	if err != nil {
780		return []Update{}, err
781	}
782
783	var updates []Update
784	json.Unmarshal(resp.Result, &updates)
785
786	if bot.Debug {
787		log.Printf("getUpdates: %+v\n", updates)
788	}
789
790	return updates, nil
791}
792
793// SetWebhook sets a webhook.
794// If this is set, GetUpdates will not get any data!
795//
796// Requires URL OR to set Clear to true.
797func (bot *BotAPI) SetWebhook(config WebhookConfig) (APIResponse, error) {
798	if config.Certificate == nil {
799		v := url.Values{}
800		if !config.Clear {
801			v.Add("url", config.URL.String())
802		}
803
804		return bot.MakeRequest("setWebhook", v)
805	}
806
807	params := make(map[string]string)
808	params["url"] = config.URL.String()
809
810	resp, err := bot.UploadFile("setWebhook", params, "certificate", config.Certificate)
811	if err != nil {
812		return APIResponse{}, err
813	}
814
815	var apiResp APIResponse
816	json.Unmarshal(resp.Result, &apiResp)
817
818	if bot.Debug {
819		log.Printf("setWebhook resp: %+v\n", apiResp)
820	}
821
822	return apiResp, nil
823}
824
825// UpdatesChan starts a channel for getting updates.
826func (bot *BotAPI) UpdatesChan(config UpdateConfig) error {
827	bot.Updates = make(chan Update, 100)
828
829	go func() {
830		for {
831			updates, err := bot.GetUpdates(config)
832			if err != nil {
833				log.Println(err)
834				log.Println("Failed to get updates, retrying in 3 seconds...")
835				time.Sleep(time.Second * 3)
836
837				continue
838			}
839
840			for _, update := range updates {
841				if update.UpdateID >= config.Offset {
842					config.Offset = update.UpdateID + 1
843					bot.Updates <- update
844				}
845			}
846		}
847	}()
848
849	return nil
850}
851
852// ListenForWebhook registers a http handler for a webhook.
853func (bot *BotAPI) ListenForWebhook(pattern string) {
854	bot.Updates = make(chan Update, 100)
855
856	http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
857		bytes, _ := ioutil.ReadAll(r.Body)
858
859		var update Update
860		json.Unmarshal(bytes, &update)
861
862		bot.Updates <- update
863	})
864}