all repos — telegram-bot-api @ ffe77fb717d3cda2df08269a6362c49ccab11ebf

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	res, err := bot.Client.Do(req)
212	if err != nil {
213		return APIResponse{}, err
214	}
215	defer res.Body.Close()
216
217	bytes, err := ioutil.ReadAll(res.Body)
218	if err != nil {
219		return APIResponse{}, err
220	}
221
222	if bot.Debug {
223		log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes))
224	}
225
226	var apiResp APIResponse
227
228	err = json.Unmarshal(bytes, &apiResp)
229	if err != nil {
230		return APIResponse{}, err
231	}
232
233	if !apiResp.Ok {
234		return APIResponse{}, errors.New(apiResp.Description)
235	}
236
237	return apiResp, nil
238}
239
240// GetFileDirectURL returns direct URL to file
241//
242// It requires the FileID.
243func (bot *BotAPI) GetFileDirectURL(fileID string) (string, error) {
244	file, err := bot.GetFile(FileConfig{fileID})
245
246	if err != nil {
247		return "", err
248	}
249
250	return file.Link(bot.Token), nil
251}
252
253// GetMe fetches the currently authenticated bot.
254//
255// This method is called upon creation to validate the token,
256// and so you may get this data from BotAPI.Self without the need for
257// another request.
258func (bot *BotAPI) GetMe() (User, error) {
259	resp, err := bot.MakeRequest("getMe", nil)
260	if err != nil {
261		return User{}, err
262	}
263
264	var user User
265	err = json.Unmarshal(resp.Result, &user)
266
267	return user, err
268}
269
270// IsMessageToMe returns true if message directed to this bot.
271//
272// It requires the Message.
273func (bot *BotAPI) IsMessageToMe(message Message) bool {
274	return strings.Contains(message.Text, "@"+bot.Self.UserName)
275}
276
277// Request sends a Chattable to Telegram, and returns the APIResponse.
278func (bot *BotAPI) Request(c Chattable) (APIResponse, error) {
279	params, err := c.params()
280	if err != nil {
281		return APIResponse{}, err
282	}
283
284	switch t := c.(type) {
285	case Fileable:
286		if t.useExistingFile() {
287			return bot.MakeRequest(t.method(), params)
288		}
289
290		return bot.UploadFile(t.method(), params, t.name(), t.getFile())
291	default:
292		return bot.MakeRequest(c.method(), params)
293	}
294}
295
296// Send will send a Chattable item to Telegram and provides the
297// returned Message.
298func (bot *BotAPI) Send(c Chattable) (Message, error) {
299	resp, err := bot.Request(c)
300	if err != nil {
301		return Message{}, err
302	}
303
304	var message Message
305	err = json.Unmarshal(resp.Result, &message)
306
307	return message, err
308}
309
310// SendMediaGroup sends a media group and returns the resulting messages.
311func (bot *BotAPI) SendMediaGroup(config MediaGroupConfig) ([]Message, error) {
312	params, _ := config.params()
313
314	resp, err := bot.MakeRequest(config.method(), params)
315	if err != nil {
316		return nil, err
317	}
318
319	var messages []Message
320	err = json.Unmarshal(resp.Result, &messages)
321
322	return messages, err
323}
324
325// GetUserProfilePhotos gets a user's profile photos.
326//
327// It requires UserID.
328// Offset and Limit are optional.
329func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) {
330	params, _ := config.params()
331
332	resp, err := bot.MakeRequest(config.method(), params)
333	if err != nil {
334		return UserProfilePhotos{}, err
335	}
336
337	var profilePhotos UserProfilePhotos
338	err = json.Unmarshal(resp.Result, &profilePhotos)
339
340	return profilePhotos, err
341}
342
343// GetFile returns a File which can download a file from Telegram.
344//
345// Requires FileID.
346func (bot *BotAPI) GetFile(config FileConfig) (File, error) {
347	params, _ := config.params()
348
349	resp, err := bot.MakeRequest(config.method(), params)
350	if err != nil {
351		return File{}, err
352	}
353
354	var file File
355	err = json.Unmarshal(resp.Result, &file)
356
357	return file, err
358}
359
360// GetUpdates fetches updates.
361// If a WebHook is set, this will not return any data!
362//
363// Offset, Limit, and Timeout are optional.
364// To avoid stale items, set Offset to one higher than the previous item.
365// Set Timeout to a large number to reduce requests so you can get updates
366// instantly instead of having to wait between requests.
367func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) {
368	params, _ := config.params()
369
370	resp, err := bot.MakeRequest(config.method(), params)
371	if err != nil {
372		return []Update{}, err
373	}
374
375	var updates []Update
376	err = json.Unmarshal(resp.Result, &updates)
377
378	return updates, err
379}
380
381// GetWebhookInfo allows you to fetch information about a webhook and if
382// one currently is set, along with pending update count and error messages.
383func (bot *BotAPI) GetWebhookInfo() (WebhookInfo, error) {
384	resp, err := bot.MakeRequest("getWebhookInfo", nil)
385	if err != nil {
386		return WebhookInfo{}, err
387	}
388
389	var info WebhookInfo
390	err = json.Unmarshal(resp.Result, &info)
391
392	return info, err
393}
394
395// GetUpdatesChan starts and returns a channel for getting updates.
396func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) UpdatesChannel {
397	ch := make(chan Update, bot.Buffer)
398
399	go func() {
400		for {
401			select {
402			case <-bot.shutdownChannel:
403				return
404			default:
405			}
406
407			updates, err := bot.GetUpdates(config)
408			if err != nil {
409				log.Println(err)
410				log.Println("Failed to get updates, retrying in 3 seconds...")
411				time.Sleep(time.Second * 3)
412
413				continue
414			}
415
416			for _, update := range updates {
417				if update.UpdateID >= config.Offset {
418					config.Offset = update.UpdateID + 1
419					ch <- update
420				}
421			}
422		}
423	}()
424
425	return ch
426}
427
428// StopReceivingUpdates stops the go routine which receives updates
429func (bot *BotAPI) StopReceivingUpdates() {
430	if bot.Debug {
431		log.Println("Stopping the update receiver routine...")
432	}
433	close(bot.shutdownChannel)
434}
435
436// ListenForWebhook registers a http handler for a webhook.
437func (bot *BotAPI) ListenForWebhook(pattern string) UpdatesChannel {
438	ch := make(chan Update, bot.Buffer)
439
440	http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
441		bytes, _ := ioutil.ReadAll(r.Body)
442
443		var update Update
444		json.Unmarshal(bytes, &update)
445
446		ch <- update
447	})
448
449	return ch
450}
451
452// GetChat gets information about a chat.
453func (bot *BotAPI) GetChat(config ChatInfoConfig) (Chat, error) {
454	params, _ := config.params()
455
456	resp, err := bot.MakeRequest(config.method(), params)
457	if err != nil {
458		return Chat{}, err
459	}
460
461	var chat Chat
462	err = json.Unmarshal(resp.Result, &chat)
463
464	return chat, err
465}
466
467// GetChatAdministrators gets a list of administrators in the chat.
468//
469// If none have been appointed, only the creator will be returned.
470// Bots are not shown, even if they are an administrator.
471func (bot *BotAPI) GetChatAdministrators(config ChatAdministratorsConfig) ([]ChatMember, error) {
472	params, _ := config.params()
473
474	resp, err := bot.MakeRequest(config.method(), params)
475	if err != nil {
476		return []ChatMember{}, err
477	}
478
479	var members []ChatMember
480	err = json.Unmarshal(resp.Result, &members)
481
482	return members, err
483}
484
485// GetChatMembersCount gets the number of users in a chat.
486func (bot *BotAPI) GetChatMembersCount(config ChatMemberCountConfig) (int, error) {
487	params, _ := config.params()
488
489	resp, err := bot.MakeRequest(config.method(), params)
490	if err != nil {
491		return -1, err
492	}
493
494	var count int
495	err = json.Unmarshal(resp.Result, &count)
496
497	return count, err
498}
499
500// GetChatMember gets a specific chat member.
501func (bot *BotAPI) GetChatMember(config GetChatMemberConfig) (ChatMember, error) {
502	params, _ := config.params()
503
504	resp, err := bot.MakeRequest(config.method(), params)
505	if err != nil {
506		return ChatMember{}, err
507	}
508
509	var member ChatMember
510	err = json.Unmarshal(resp.Result, &member)
511
512	return member, err
513}
514
515// GetGameHighScores allows you to get the high scores for a game.
516func (bot *BotAPI) GetGameHighScores(config GetGameHighScoresConfig) ([]GameHighScore, error) {
517	params, _ := config.params()
518
519	resp, err := bot.MakeRequest(config.method(), params)
520	if err != nil {
521		return []GameHighScore{}, err
522	}
523
524	var highScores []GameHighScore
525	err = json.Unmarshal(resp.Result, &highScores)
526
527	return highScores, err
528}
529
530// GetInviteLink get InviteLink for a chat
531func (bot *BotAPI) GetInviteLink(config ChatInviteLinkConfig) (string, error) {
532	params, _ := config.params()
533
534	resp, err := bot.MakeRequest(config.method(), params)
535	if err != nil {
536		return "", err
537	}
538
539	var inviteLink string
540	err = json.Unmarshal(resp.Result, &inviteLink)
541
542	return inviteLink, err
543}
544
545// GetStickerSet returns a StickerSet.
546func (bot *BotAPI) GetStickerSet(config GetStickerSetConfig) (StickerSet, error) {
547	params, _ := config.params()
548
549	resp, err := bot.MakeRequest(config.method(), params)
550	if err != nil {
551		return StickerSet{}, err
552	}
553
554	var stickers StickerSet
555	err = json.Unmarshal(resp.Result, &stickers)
556
557	return stickers, err
558}
559
560// StopPoll stops a poll and returns the result.
561func (bot *BotAPI) StopPoll(config StopPollConfig) (Poll, error) {
562	params, err := config.params()
563	if err != nil {
564		return Poll{}, err
565	}
566
567	resp, err := bot.MakeRequest(config.method(), params)
568	if err != nil {
569		return Poll{}, err
570	}
571
572	var poll Poll
573	err = json.Unmarshal(resp.Result, &poll)
574
575	return poll, err
576}