all repos — telegram-bot-api @ cb3a14a3b5d4216fd32fd6554cf3477616c697ed

Golang bindings for the Telegram Bot API

bot.go (view raw)

  1// Package tgbotapi has functions and types used for interacting with
  2// the Telegram Bot API.
  3package tgbotapi
  4
  5import (
  6	"bytes"
  7	"encoding/json"
  8	"errors"
  9	"fmt"
 10	"io"
 11	"io/ioutil"
 12	"net/http"
 13	"net/url"
 14	"os"
 15	"strings"
 16	"time"
 17
 18	"github.com/technoweenie/multipartstreamer"
 19)
 20
 21// BotAPI allows you to interact with the Telegram Bot API.
 22type BotAPI struct {
 23	Token  string `json:"token"`
 24	Debug  bool   `json:"debug"`
 25	Buffer int    `json:"buffer"`
 26
 27	Self            User         `json:"-"`
 28	Client          *http.Client `json:"-"`
 29	shutdownChannel chan interface{}
 30}
 31
 32// NewBotAPI creates a new BotAPI instance.
 33//
 34// It requires a token, provided by @BotFather on Telegram.
 35func NewBotAPI(token string) (*BotAPI, error) {
 36	return NewBotAPIWithClient(token, &http.Client{})
 37}
 38
 39// NewBotAPIWithClient creates a new BotAPI instance
 40// and allows you to pass a http.Client.
 41//
 42// It requires a token, provided by @BotFather on Telegram.
 43func NewBotAPIWithClient(token string, client *http.Client) (*BotAPI, error) {
 44	bot := &BotAPI{
 45		Token:           token,
 46		Client:          client,
 47		Buffer:          100,
 48		shutdownChannel: make(chan interface{}),
 49	}
 50
 51	self, err := bot.GetMe()
 52	if err != nil {
 53		return nil, err
 54	}
 55
 56	bot.Self = self
 57
 58	return bot, nil
 59}
 60
 61func buildParams(in Params) (out url.Values) {
 62	if in == nil {
 63		return url.Values{}
 64	}
 65
 66	out = url.Values{}
 67
 68	for key, value := range in {
 69		out.Set(key, value)
 70	}
 71
 72	return
 73}
 74
 75// MakeRequest makes a request to a specific endpoint with our token.
 76func (bot *BotAPI) MakeRequest(endpoint string, params Params) (APIResponse, error) {
 77	if bot.Debug {
 78		log.Printf("Endpoint: %s, params: %v\n", endpoint, params)
 79	}
 80
 81	method := fmt.Sprintf(APIEndpoint, bot.Token, endpoint)
 82
 83	values := buildParams(params)
 84
 85	resp, err := bot.Client.PostForm(method, values)
 86	if err != nil {
 87		return APIResponse{}, err
 88	}
 89	defer resp.Body.Close()
 90
 91	var apiResp APIResponse
 92	bytes, err := bot.decodeAPIResponse(resp.Body, &apiResp)
 93	if err != nil {
 94		return apiResp, err
 95	}
 96
 97	if bot.Debug {
 98		log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes))
 99	}
100
101	if !apiResp.Ok {
102		var parameters ResponseParameters
103
104		if apiResp.Parameters != nil {
105			parameters = *apiResp.Parameters
106		}
107
108		return apiResp, Error{
109			Message:            apiResp.Description,
110			ResponseParameters: parameters,
111		}
112	}
113
114	return apiResp, nil
115}
116
117// decodeAPIResponse decode response and return slice of bytes if debug enabled.
118// If debug disabled, just decode http.Response.Body stream to APIResponse struct
119// for efficient memory usage
120func (bot *BotAPI) decodeAPIResponse(responseBody io.Reader, resp *APIResponse) (_ []byte, err error) {
121	if !bot.Debug {
122		dec := json.NewDecoder(responseBody)
123		err = dec.Decode(resp)
124		return
125	}
126
127	// if debug, read reponse body
128	data, err := ioutil.ReadAll(responseBody)
129	if err != nil {
130		return
131	}
132
133	err = json.Unmarshal(data, resp)
134	if err != nil {
135		return
136	}
137
138	return data, nil
139}
140
141// UploadFile makes a request to the API with a file.
142//
143// Requires the parameter to hold the file not be in the params.
144// File should be a string to a file path, a FileBytes struct,
145// a FileReader struct, or a url.URL.
146//
147// Note that if your FileReader has a size set to -1, it will read
148// the file into memory to calculate a size.
149func (bot *BotAPI) UploadFile(endpoint string, params Params, fieldname string, file interface{}) (APIResponse, error) {
150	ms := multipartstreamer.New()
151
152	switch f := file.(type) {
153	case string:
154		ms.WriteFields(params)
155
156		fileHandle, err := os.Open(f)
157		if err != nil {
158			return APIResponse{}, err
159		}
160		defer fileHandle.Close()
161
162		fi, err := os.Stat(f)
163		if err != nil {
164			return APIResponse{}, err
165		}
166
167		ms.WriteReader(fieldname, fileHandle.Name(), fi.Size(), fileHandle)
168	case FileBytes:
169		ms.WriteFields(params)
170
171		buf := bytes.NewBuffer(f.Bytes)
172		ms.WriteReader(fieldname, f.Name, int64(len(f.Bytes)), buf)
173	case FileReader:
174		ms.WriteFields(params)
175
176		if f.Size != -1 {
177			ms.WriteReader(fieldname, f.Name, f.Size, f.Reader)
178
179			break
180		}
181
182		data, err := ioutil.ReadAll(f.Reader)
183		if err != nil {
184			return APIResponse{}, err
185		}
186
187		buf := bytes.NewBuffer(data)
188
189		ms.WriteReader(fieldname, f.Name, int64(len(data)), buf)
190	case url.URL:
191		params[fieldname] = f.String()
192
193		ms.WriteFields(params)
194	default:
195		return APIResponse{}, errors.New(ErrBadFileType)
196	}
197
198	if bot.Debug {
199		log.Printf("Endpoint: %s, fieldname: %s, params: %v, file: %T\n", endpoint, fieldname, params, file)
200	}
201
202	method := fmt.Sprintf(APIEndpoint, bot.Token, endpoint)
203
204	req, err := http.NewRequest("POST", method, nil)
205	if err != nil {
206		return APIResponse{}, err
207	}
208
209	ms.SetupRequest(req)
210
211	resp, err := bot.Client.Do(req)
212	if err != nil {
213		return APIResponse{}, err
214	}
215	defer resp.Body.Close()
216
217	var apiResp APIResponse
218	bytes, err := bot.decodeAPIResponse(resp.Body, &apiResp)
219	if err != nil {
220		return apiResp, err
221	}
222
223	if bot.Debug {
224		log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes))
225	}
226
227	if !apiResp.Ok {
228		var parameters ResponseParameters
229
230		if apiResp.Parameters != nil {
231			parameters = *apiResp.Parameters
232		}
233
234		return apiResp, Error{
235			Message:            apiResp.Description,
236			ResponseParameters: parameters,
237		}
238	}
239
240	return apiResp, nil
241}
242
243// GetFileDirectURL returns direct URL to file
244//
245// It requires the FileID.
246func (bot *BotAPI) GetFileDirectURL(fileID string) (string, error) {
247	file, err := bot.GetFile(FileConfig{fileID})
248
249	if err != nil {
250		return "", err
251	}
252
253	return file.Link(bot.Token), nil
254}
255
256// GetMe fetches the currently authenticated bot.
257//
258// This method is called upon creation to validate the token,
259// and so you may get this data from BotAPI.Self without the need for
260// another request.
261func (bot *BotAPI) GetMe() (User, error) {
262	resp, err := bot.MakeRequest("getMe", nil)
263	if err != nil {
264		return User{}, err
265	}
266
267	var user User
268	err = json.Unmarshal(resp.Result, &user)
269
270	return user, err
271}
272
273// IsMessageToMe returns true if message directed to this bot.
274//
275// It requires the Message.
276func (bot *BotAPI) IsMessageToMe(message Message) bool {
277	return strings.Contains(message.Text, "@"+bot.Self.UserName)
278}
279
280// Request sends a Chattable to Telegram, and returns the APIResponse.
281func (bot *BotAPI) Request(c Chattable) (APIResponse, error) {
282	params, err := c.params()
283	if err != nil {
284		return APIResponse{}, err
285	}
286
287	switch t := c.(type) {
288	case Fileable:
289		if t.useExistingFile() {
290			return bot.MakeRequest(t.method(), params)
291		}
292
293		return bot.UploadFile(t.method(), params, t.name(), t.getFile())
294	default:
295		return bot.MakeRequest(c.method(), params)
296	}
297}
298
299// Send will send a Chattable item to Telegram and provides the
300// returned Message.
301func (bot *BotAPI) Send(c Chattable) (Message, error) {
302	resp, err := bot.Request(c)
303	if err != nil {
304		return Message{}, err
305	}
306
307	var message Message
308	err = json.Unmarshal(resp.Result, &message)
309
310	return message, err
311}
312
313// GetUserProfilePhotos gets a user's profile photos.
314//
315// It requires UserID.
316// Offset and Limit are optional.
317func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) {
318	params, _ := config.params()
319
320	resp, err := bot.MakeRequest(config.method(), params)
321	if err != nil {
322		return UserProfilePhotos{}, err
323	}
324
325	var profilePhotos UserProfilePhotos
326	err = json.Unmarshal(resp.Result, &profilePhotos)
327
328	return profilePhotos, err
329}
330
331// GetFile returns a File which can download a file from Telegram.
332//
333// Requires FileID.
334func (bot *BotAPI) GetFile(config FileConfig) (File, error) {
335	params, _ := config.params()
336
337	resp, err := bot.MakeRequest(config.method(), params)
338	if err != nil {
339		return File{}, err
340	}
341
342	var file File
343	err = json.Unmarshal(resp.Result, &file)
344
345	return file, err
346}
347
348// GetUpdates fetches updates.
349// If a WebHook is set, this will not return any data!
350//
351// Offset, Limit, and Timeout are optional.
352// To avoid stale items, set Offset to one higher than the previous item.
353// Set Timeout to a large number to reduce requests so you can get updates
354// instantly instead of having to wait between requests.
355func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) {
356	params, _ := config.params()
357
358	resp, err := bot.MakeRequest(config.method(), params)
359	if err != nil {
360		return []Update{}, err
361	}
362
363	var updates []Update
364	err = json.Unmarshal(resp.Result, &updates)
365
366	return updates, err
367}
368
369// GetWebhookInfo allows you to fetch information about a webhook and if
370// one currently is set, along with pending update count and error messages.
371func (bot *BotAPI) GetWebhookInfo() (WebhookInfo, error) {
372	resp, err := bot.MakeRequest("getWebhookInfo", nil)
373	if err != nil {
374		return WebhookInfo{}, err
375	}
376
377	var info WebhookInfo
378	err = json.Unmarshal(resp.Result, &info)
379
380	return info, err
381}
382
383// GetUpdatesChan starts and returns a channel for getting updates.
384func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) UpdatesChannel {
385	ch := make(chan Update, bot.Buffer)
386
387	go func() {
388		for {
389			select {
390			case <-bot.shutdownChannel:
391				return
392			default:
393			}
394
395			updates, err := bot.GetUpdates(config)
396			if err != nil {
397				log.Println(err)
398				log.Println("Failed to get updates, retrying in 3 seconds...")
399				time.Sleep(time.Second * 3)
400
401				continue
402			}
403
404			for _, update := range updates {
405				if update.UpdateID >= config.Offset {
406					config.Offset = update.UpdateID + 1
407					ch <- update
408				}
409			}
410		}
411	}()
412
413	return ch
414}
415
416// StopReceivingUpdates stops the go routine which receives updates
417func (bot *BotAPI) StopReceivingUpdates() {
418	if bot.Debug {
419		log.Println("Stopping the update receiver routine...")
420	}
421	close(bot.shutdownChannel)
422}
423
424// ListenForWebhook registers a http handler for a webhook.
425func (bot *BotAPI) ListenForWebhook(pattern string) UpdatesChannel {
426	ch := make(chan Update, bot.Buffer)
427
428	http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
429		bytes, _ := ioutil.ReadAll(r.Body)
430
431		var update Update
432		json.Unmarshal(bytes, &update)
433
434		ch <- update
435	})
436
437	return ch
438}
439
440// GetChat gets information about a chat.
441func (bot *BotAPI) GetChat(config ChatInfoConfig) (Chat, error) {
442	params, _ := config.params()
443
444	resp, err := bot.MakeRequest(config.method(), params)
445	if err != nil {
446		return Chat{}, err
447	}
448
449	var chat Chat
450	err = json.Unmarshal(resp.Result, &chat)
451
452	return chat, err
453}
454
455// GetChatAdministrators gets a list of administrators in the chat.
456//
457// If none have been appointed, only the creator will be returned.
458// Bots are not shown, even if they are an administrator.
459func (bot *BotAPI) GetChatAdministrators(config ChatAdministratorsConfig) ([]ChatMember, error) {
460	params, _ := config.params()
461
462	resp, err := bot.MakeRequest(config.method(), params)
463	if err != nil {
464		return []ChatMember{}, err
465	}
466
467	var members []ChatMember
468	err = json.Unmarshal(resp.Result, &members)
469
470	return members, err
471}
472
473// GetChatMembersCount gets the number of users in a chat.
474func (bot *BotAPI) GetChatMembersCount(config ChatMemberCountConfig) (int, error) {
475	params, _ := config.params()
476
477	resp, err := bot.MakeRequest(config.method(), params)
478	if err != nil {
479		return -1, err
480	}
481
482	var count int
483	err = json.Unmarshal(resp.Result, &count)
484
485	return count, err
486}
487
488// GetChatMember gets a specific chat member.
489func (bot *BotAPI) GetChatMember(config GetChatMemberConfig) (ChatMember, error) {
490	params, _ := config.params()
491
492	resp, err := bot.MakeRequest(config.method(), params)
493	if err != nil {
494		return ChatMember{}, err
495	}
496
497	var member ChatMember
498	err = json.Unmarshal(resp.Result, &member)
499
500	return member, err
501}
502
503// GetGameHighScores allows you to get the high scores for a game.
504func (bot *BotAPI) GetGameHighScores(config GetGameHighScoresConfig) ([]GameHighScore, error) {
505	params, _ := config.params()
506
507	resp, err := bot.MakeRequest(config.method(), params)
508	if err != nil {
509		return []GameHighScore{}, err
510	}
511
512	var highScores []GameHighScore
513	err = json.Unmarshal(resp.Result, &highScores)
514
515	return highScores, err
516}
517
518// GetInviteLink get InviteLink for a chat
519func (bot *BotAPI) GetInviteLink(config ChatInviteLinkConfig) (string, error) {
520	params, _ := config.params()
521
522	resp, err := bot.MakeRequest(config.method(), params)
523	if err != nil {
524		return "", err
525	}
526
527	var inviteLink string
528	err = json.Unmarshal(resp.Result, &inviteLink)
529
530	return inviteLink, err
531}
532
533// GetStickerSet returns a StickerSet.
534func (bot *BotAPI) GetStickerSet(config GetStickerSetConfig) (StickerSet, error) {
535	params, _ := config.params()
536
537	resp, err := bot.MakeRequest(config.method(), params)
538	if err != nil {
539		return StickerSet{}, nil
540	}
541
542	var stickers StickerSet
543	err = json.Unmarshal(resp.Result, &stickers)
544
545	return stickers, err
546}