all repos — telegram-bot-api @ a0a678302e8953531aa87a6a7e97a395c8954ada

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// GetUserProfilePhotos gets a user's profile photos.
311//
312// It requires UserID.
313// Offset and Limit are optional.
314func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) {
315	params, _ := config.params()
316
317	resp, err := bot.MakeRequest(config.method(), params)
318	if err != nil {
319		return UserProfilePhotos{}, err
320	}
321
322	var profilePhotos UserProfilePhotos
323	err = json.Unmarshal(resp.Result, &profilePhotos)
324
325	return profilePhotos, err
326}
327
328// GetFile returns a File which can download a file from Telegram.
329//
330// Requires FileID.
331func (bot *BotAPI) GetFile(config FileConfig) (File, error) {
332	params, _ := config.params()
333
334	resp, err := bot.MakeRequest(config.method(), params)
335	if err != nil {
336		return File{}, err
337	}
338
339	var file File
340	err = json.Unmarshal(resp.Result, &file)
341
342	return file, err
343}
344
345// GetUpdates fetches updates.
346// If a WebHook is set, this will not return any data!
347//
348// Offset, Limit, and Timeout are optional.
349// To avoid stale items, set Offset to one higher than the previous item.
350// Set Timeout to a large number to reduce requests so you can get updates
351// instantly instead of having to wait between requests.
352func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) {
353	params, _ := config.params()
354
355	resp, err := bot.MakeRequest(config.method(), params)
356	if err != nil {
357		return []Update{}, err
358	}
359
360	var updates []Update
361	err = json.Unmarshal(resp.Result, &updates)
362
363	return updates, err
364}
365
366// GetWebhookInfo allows you to fetch information about a webhook and if
367// one currently is set, along with pending update count and error messages.
368func (bot *BotAPI) GetWebhookInfo() (WebhookInfo, error) {
369	resp, err := bot.MakeRequest("getWebhookInfo", nil)
370	if err != nil {
371		return WebhookInfo{}, err
372	}
373
374	var info WebhookInfo
375	err = json.Unmarshal(resp.Result, &info)
376
377	return info, err
378}
379
380// GetUpdatesChan starts and returns a channel for getting updates.
381func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) UpdatesChannel {
382	ch := make(chan Update, bot.Buffer)
383
384	go func() {
385		for {
386			select {
387			case <-bot.shutdownChannel:
388				return
389			default:
390			}
391
392			updates, err := bot.GetUpdates(config)
393			if err != nil {
394				log.Println(err)
395				log.Println("Failed to get updates, retrying in 3 seconds...")
396				time.Sleep(time.Second * 3)
397
398				continue
399			}
400
401			for _, update := range updates {
402				if update.UpdateID >= config.Offset {
403					config.Offset = update.UpdateID + 1
404					ch <- update
405				}
406			}
407		}
408	}()
409
410	return ch
411}
412
413// StopReceivingUpdates stops the go routine which receives updates
414func (bot *BotAPI) StopReceivingUpdates() {
415	if bot.Debug {
416		log.Println("Stopping the update receiver routine...")
417	}
418	close(bot.shutdownChannel)
419}
420
421// ListenForWebhook registers a http handler for a webhook.
422func (bot *BotAPI) ListenForWebhook(pattern string) UpdatesChannel {
423	ch := make(chan Update, bot.Buffer)
424
425	http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
426		bytes, _ := ioutil.ReadAll(r.Body)
427
428		var update Update
429		json.Unmarshal(bytes, &update)
430
431		ch <- update
432	})
433
434	return ch
435}
436
437// GetChat gets information about a chat.
438func (bot *BotAPI) GetChat(config ChatInfoConfig) (Chat, error) {
439	params, _ := config.params()
440
441	resp, err := bot.MakeRequest(config.method(), params)
442	if err != nil {
443		return Chat{}, err
444	}
445
446	var chat Chat
447	err = json.Unmarshal(resp.Result, &chat)
448
449	return chat, err
450}
451
452// GetChatAdministrators gets a list of administrators in the chat.
453//
454// If none have been appointed, only the creator will be returned.
455// Bots are not shown, even if they are an administrator.
456func (bot *BotAPI) GetChatAdministrators(config ChatAdministratorsConfig) ([]ChatMember, error) {
457	params, _ := config.params()
458
459	resp, err := bot.MakeRequest(config.method(), params)
460	if err != nil {
461		return []ChatMember{}, err
462	}
463
464	var members []ChatMember
465	err = json.Unmarshal(resp.Result, &members)
466
467	return members, err
468}
469
470// GetChatMembersCount gets the number of users in a chat.
471func (bot *BotAPI) GetChatMembersCount(config ChatMemberCountConfig) (int, error) {
472	params, _ := config.params()
473
474	resp, err := bot.MakeRequest(config.method(), params)
475	if err != nil {
476		return -1, err
477	}
478
479	var count int
480	err = json.Unmarshal(resp.Result, &count)
481
482	return count, err
483}
484
485// GetChatMember gets a specific chat member.
486func (bot *BotAPI) GetChatMember(config GetChatMemberConfig) (ChatMember, error) {
487	params, _ := config.params()
488
489	resp, err := bot.MakeRequest(config.method(), params)
490	if err != nil {
491		return ChatMember{}, err
492	}
493
494	var member ChatMember
495	err = json.Unmarshal(resp.Result, &member)
496
497	return member, err
498}
499
500// GetGameHighScores allows you to get the high scores for a game.
501func (bot *BotAPI) GetGameHighScores(config GetGameHighScoresConfig) ([]GameHighScore, error) {
502	params, _ := config.params()
503
504	resp, err := bot.MakeRequest(config.method(), params)
505	if err != nil {
506		return []GameHighScore{}, err
507	}
508
509	var highScores []GameHighScore
510	err = json.Unmarshal(resp.Result, &highScores)
511
512	return highScores, err
513}
514
515// GetInviteLink get InviteLink for a chat
516func (bot *BotAPI) GetInviteLink(config ChatInviteLinkConfig) (string, error) {
517	params, _ := config.params()
518
519	resp, err := bot.MakeRequest(config.method(), params)
520	if err != nil {
521		return "", err
522	}
523
524	var inviteLink string
525	err = json.Unmarshal(resp.Result, &inviteLink)
526
527	return inviteLink, err
528}
529
530// GetStickerSet returns a StickerSet.
531func (bot *BotAPI) GetStickerSet(config GetStickerSetConfig) (StickerSet, error) {
532	params, _ := config.params()
533
534	resp, err := bot.MakeRequest(config.method(), params)
535	if err != nil {
536		return StickerSet{}, nil
537	}
538
539	var stickers StickerSet
540	err = json.Unmarshal(resp.Result, &stickers)
541
542	return stickers, err
543}