all repos — telegram-bot-api @ 774f1e72e7647a5e78d4d3345f54581249bbf110

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