all repos — telegram-bot-api @ 4a76ae1bfbb18109ad29515073cad236c6115173

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