all repos — telegram-bot-api @ 63cbbdc63c09713ecb0d449236568b7fa18e4e6d

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