all repos — telegram-bot-api @ bb07769ea9a507112da471c1df8f0d28eacaef31

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/ioutil"
 11	"log"
 12	"net/http"
 13	"net/url"
 14	"os"
 15	"strconv"
 16	"strings"
 17	"time"
 18
 19	"github.com/technoweenie/multipartstreamer"
 20)
 21
 22// BotAPI allows you to interact with the Telegram Bot API.
 23type BotAPI struct {
 24	Token  string `json:"token"`
 25	Debug  bool   `json:"debug"`
 26	Buffer int    `json:"buffer"`
 27
 28	Self   User         `json:"-"`
 29	Client *http.Client `json:"-"`
 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	}
 49
 50	self, err := bot.GetMe()
 51	if err != nil {
 52		return nil, err
 53	}
 54
 55	bot.Self = self
 56
 57	return bot, nil
 58}
 59
 60// MakeRequest makes a request to a specific endpoint with our token.
 61func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse, error) {
 62	if bot.Debug {
 63		log.Printf("Endpoint: %s, values: %v\n", endpoint, params)
 64	}
 65
 66	method := fmt.Sprintf(APIEndpoint, bot.Token, endpoint)
 67
 68	resp, err := bot.Client.PostForm(method, params)
 69	if err != nil {
 70		return APIResponse{}, err
 71	}
 72	defer resp.Body.Close()
 73
 74	bytes, err := ioutil.ReadAll(resp.Body)
 75	if err != nil {
 76		return APIResponse{}, err
 77	}
 78
 79	if bot.Debug {
 80		log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes))
 81	}
 82
 83	var apiResp APIResponse
 84	err = json.Unmarshal(bytes, &apiResp)
 85	if err != nil {
 86		return APIResponse{}, err
 87	}
 88
 89	if !apiResp.Ok {
 90		return apiResp, errors.New(apiResp.Description)
 91	}
 92
 93	return apiResp, nil
 94}
 95
 96// UploadFile makes a request to the API with a file.
 97//
 98// Requires the parameter to hold the file not be in the params.
 99// File should be a string to a file path, a FileBytes struct,
100// a FileReader struct, or a url.URL.
101//
102// Note that if your FileReader has a size set to -1, it will read
103// the file into memory to calculate a size.
104func (bot *BotAPI) UploadFile(endpoint string, params map[string]string, fieldname string, file interface{}) (APIResponse, error) {
105	ms := multipartstreamer.New()
106
107	switch f := file.(type) {
108	case string:
109		ms.WriteFields(params)
110
111		fileHandle, err := os.Open(f)
112		if err != nil {
113			return APIResponse{}, err
114		}
115		defer fileHandle.Close()
116
117		fi, err := os.Stat(f)
118		if err != nil {
119			return APIResponse{}, err
120		}
121
122		ms.WriteReader(fieldname, fileHandle.Name(), fi.Size(), fileHandle)
123	case FileBytes:
124		ms.WriteFields(params)
125
126		buf := bytes.NewBuffer(f.Bytes)
127		ms.WriteReader(fieldname, f.Name, int64(len(f.Bytes)), buf)
128	case FileReader:
129		ms.WriteFields(params)
130
131		if f.Size != -1 {
132			ms.WriteReader(fieldname, f.Name, f.Size, f.Reader)
133
134			break
135		}
136
137		data, err := ioutil.ReadAll(f.Reader)
138		if err != nil {
139			return APIResponse{}, err
140		}
141
142		buf := bytes.NewBuffer(data)
143
144		ms.WriteReader(fieldname, f.Name, int64(len(data)), buf)
145	case url.URL:
146		params[fieldname] = f.String()
147
148		ms.WriteFields(params)
149	default:
150		return APIResponse{}, errors.New(ErrBadFileType)
151	}
152
153	if bot.Debug {
154		log.Printf("Endpoint: %s, fieldname: %s, params: %v, file: %T\n", endpoint, fieldname, params, file)
155	}
156
157	method := fmt.Sprintf(APIEndpoint, bot.Token, endpoint)
158
159	req, err := http.NewRequest("POST", method, nil)
160	if err != nil {
161		return APIResponse{}, err
162	}
163
164	ms.SetupRequest(req)
165
166	res, err := bot.Client.Do(req)
167	if err != nil {
168		return APIResponse{}, err
169	}
170	defer res.Body.Close()
171
172	bytes, err := ioutil.ReadAll(res.Body)
173	if err != nil {
174		return APIResponse{}, err
175	}
176
177	if bot.Debug {
178		log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes))
179	}
180
181	var apiResp APIResponse
182
183	err = json.Unmarshal(bytes, &apiResp)
184	if err != nil {
185		return APIResponse{}, err
186	}
187
188	if !apiResp.Ok {
189		return APIResponse{}, errors.New(apiResp.Description)
190	}
191
192	return apiResp, nil
193}
194
195// GetFileDirectURL returns direct URL to file
196//
197// It requires the FileID.
198func (bot *BotAPI) GetFileDirectURL(fileID string) (string, error) {
199	file, err := bot.GetFile(FileConfig{fileID})
200
201	if err != nil {
202		return "", err
203	}
204
205	return file.Link(bot.Token), nil
206}
207
208// GetMe fetches the currently authenticated bot.
209//
210// This method is called upon creation to validate the token,
211// and so you may get this data from BotAPI.Self without the need for
212// another request.
213func (bot *BotAPI) GetMe() (User, error) {
214	resp, err := bot.MakeRequest("getMe", nil)
215	if err != nil {
216		return User{}, err
217	}
218
219	var user User
220	json.Unmarshal(resp.Result, &user)
221
222	return user, nil
223}
224
225// IsMessageToMe returns true if message directed to this bot.
226//
227// It requires the Message.
228func (bot *BotAPI) IsMessageToMe(message Message) bool {
229	return strings.Contains(message.Text, "@"+bot.Self.UserName)
230}
231
232// Request sends a Chattable to Telegram, and returns the APIResponse.
233func (bot *BotAPI) Request(c Chattable) (APIResponse, error) {
234	switch t := c.(type) {
235	case Fileable:
236		if t.useExistingFile() {
237			v, err := t.values()
238			if err != nil {
239				return APIResponse{}, err
240			}
241
242			return bot.MakeRequest(t.method(), v)
243		}
244
245		p, err := t.params()
246		if err != nil {
247			return APIResponse{}, err
248		}
249
250		return bot.UploadFile(t.method(), p, t.name(), t.getFile())
251	default:
252		v, err := c.values()
253		if err != nil {
254			return APIResponse{}, err
255		}
256
257		return bot.MakeRequest(c.method(), v)
258	}
259}
260
261// Send will send a Chattable item to Telegram and provides the
262// returned Message.
263func (bot *BotAPI) Send(c Chattable) (Message, error) {
264	resp, err := bot.Request(c)
265	if err != nil {
266		return Message{}, err
267	}
268
269	var message Message
270	err = json.Unmarshal(resp.Result, &message)
271
272	return message, err
273}
274
275// GetUserProfilePhotos gets a user's profile photos.
276//
277// It requires UserID.
278// Offset and Limit are optional.
279func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) {
280	v := url.Values{}
281	v.Add("user_id", strconv.Itoa(config.UserID))
282	if config.Offset != 0 {
283		v.Add("offset", strconv.Itoa(config.Offset))
284	}
285	if config.Limit != 0 {
286		v.Add("limit", strconv.Itoa(config.Limit))
287	}
288
289	resp, err := bot.MakeRequest("getUserProfilePhotos", v)
290	if err != nil {
291		return UserProfilePhotos{}, err
292	}
293
294	var profilePhotos UserProfilePhotos
295	json.Unmarshal(resp.Result, &profilePhotos)
296
297	return profilePhotos, nil
298}
299
300// GetFile returns a File which can download a file from Telegram.
301//
302// Requires FileID.
303func (bot *BotAPI) GetFile(config FileConfig) (File, error) {
304	v := url.Values{}
305	v.Add("file_id", config.FileID)
306
307	resp, err := bot.MakeRequest("getFile", v)
308	if err != nil {
309		return File{}, err
310	}
311
312	var file File
313	json.Unmarshal(resp.Result, &file)
314
315	return file, nil
316}
317
318// GetUpdates fetches updates.
319// If a WebHook is set, this will not return any data!
320//
321// Offset, Limit, and Timeout are optional.
322// To avoid stale items, set Offset to one higher than the previous item.
323// Set Timeout to a large number to reduce requests so you can get updates
324// instantly instead of having to wait between requests.
325func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) {
326	v := url.Values{}
327	if config.Offset != 0 {
328		v.Add("offset", strconv.Itoa(config.Offset))
329	}
330	if config.Limit > 0 {
331		v.Add("limit", strconv.Itoa(config.Limit))
332	}
333	if config.Timeout > 0 {
334		v.Add("timeout", strconv.Itoa(config.Timeout))
335	}
336
337	resp, err := bot.MakeRequest("getUpdates", v)
338	if err != nil {
339		return []Update{}, err
340	}
341
342	var updates []Update
343	json.Unmarshal(resp.Result, &updates)
344
345	return updates, nil
346}
347
348// GetWebhookInfo allows you to fetch information about a webhook and if
349// one currently is set, along with pending update count and error messages.
350func (bot *BotAPI) GetWebhookInfo() (WebhookInfo, error) {
351	resp, err := bot.MakeRequest("getWebhookInfo", url.Values{})
352	if err != nil {
353		return WebhookInfo{}, err
354	}
355
356	var info WebhookInfo
357	err = json.Unmarshal(resp.Result, &info)
358
359	return info, err
360}
361
362// GetUpdatesChan starts and returns a channel for getting updates.
363func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (UpdatesChannel, error) {
364	ch := make(chan Update, bot.Buffer)
365
366	go func() {
367		for {
368			updates, err := bot.GetUpdates(config)
369			if err != nil {
370				log.Println(err)
371				log.Println("Failed to get updates, retrying in 3 seconds...")
372				time.Sleep(time.Second * 3)
373
374				continue
375			}
376
377			for _, update := range updates {
378				if update.UpdateID >= config.Offset {
379					config.Offset = update.UpdateID + 1
380					ch <- update
381				}
382			}
383		}
384	}()
385
386	return ch, nil
387}
388
389// ListenForWebhook registers a http handler for a webhook.
390func (bot *BotAPI) ListenForWebhook(pattern string) UpdatesChannel {
391	ch := make(chan Update, bot.Buffer)
392
393	http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
394		bytes, _ := ioutil.ReadAll(r.Body)
395
396		var update Update
397		json.Unmarshal(bytes, &update)
398
399		ch <- update
400	})
401
402	return ch
403}
404
405// GetChat gets information about a chat.
406func (bot *BotAPI) GetChat(config ChatConfig) (Chat, error) {
407	v := url.Values{}
408
409	if config.SuperGroupUsername == "" {
410		v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
411	} else {
412		v.Add("chat_id", config.SuperGroupUsername)
413	}
414
415	resp, err := bot.MakeRequest("getChat", v)
416	if err != nil {
417		return Chat{}, err
418	}
419
420	var chat Chat
421	err = json.Unmarshal(resp.Result, &chat)
422
423	return chat, err
424}
425
426// GetChatAdministrators gets a list of administrators in the chat.
427//
428// If none have been appointed, only the creator will be returned.
429// Bots are not shown, even if they are an administrator.
430func (bot *BotAPI) GetChatAdministrators(config ChatConfig) ([]ChatMember, error) {
431	v := url.Values{}
432
433	if config.SuperGroupUsername == "" {
434		v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
435	} else {
436		v.Add("chat_id", config.SuperGroupUsername)
437	}
438
439	resp, err := bot.MakeRequest("getChatAdministrators", v)
440	if err != nil {
441		return []ChatMember{}, err
442	}
443
444	var members []ChatMember
445	err = json.Unmarshal(resp.Result, &members)
446
447	return members, err
448}
449
450// GetChatMembersCount gets the number of users in a chat.
451func (bot *BotAPI) GetChatMembersCount(config ChatConfig) (int, error) {
452	v := url.Values{}
453
454	if config.SuperGroupUsername == "" {
455		v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
456	} else {
457		v.Add("chat_id", config.SuperGroupUsername)
458	}
459
460	resp, err := bot.MakeRequest("getChatMembersCount", v)
461	if err != nil {
462		return -1, err
463	}
464
465	var count int
466	err = json.Unmarshal(resp.Result, &count)
467
468	return count, err
469}
470
471// GetChatMember gets a specific chat member.
472func (bot *BotAPI) GetChatMember(config ChatConfigWithUser) (ChatMember, error) {
473	v := url.Values{}
474
475	if config.SuperGroupUsername == "" {
476		v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
477	} else {
478		v.Add("chat_id", config.SuperGroupUsername)
479	}
480	v.Add("user_id", strconv.Itoa(config.UserID))
481
482	resp, err := bot.MakeRequest("getChatMember", v)
483	if err != nil {
484		return ChatMember{}, err
485	}
486
487	var member ChatMember
488	err = json.Unmarshal(resp.Result, &member)
489
490	return member, err
491}
492
493// GetGameHighScores allows you to get the high scores for a game.
494func (bot *BotAPI) GetGameHighScores(config GetGameHighScoresConfig) ([]GameHighScore, error) {
495	v, _ := config.values()
496
497	resp, err := bot.MakeRequest(config.method(), v)
498	if err != nil {
499		return []GameHighScore{}, err
500	}
501
502	var highScores []GameHighScore
503	err = json.Unmarshal(resp.Result, &highScores)
504
505	return highScores, err
506}
507
508// GetInviteLink get InviteLink for a chat
509func (bot *BotAPI) GetInviteLink(config ChatConfig) (string, error) {
510	v := url.Values{}
511
512	if config.SuperGroupUsername == "" {
513		v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
514	} else {
515		v.Add("chat_id", config.SuperGroupUsername)
516	}
517
518	resp, err := bot.MakeRequest("exportChatInviteLink", v)
519	if err != nil {
520		return "", err
521	}
522
523	var inviteLink string
524	err = json.Unmarshal(resp.Result, &inviteLink)
525
526	return inviteLink, err
527}
528
529// GetStickerSet returns a StickerSet.
530func (bot *BotAPI) GetStickerSet(config GetStickerSetConfig) (StickerSet, error) {
531	v, err := config.values()
532	if err != nil {
533		return StickerSet{}, nil
534	}
535
536	resp, err := bot.MakeRequest(config.method(), v)
537	if err != nil {
538		return StickerSet{}, nil
539	}
540
541	var stickers StickerSet
542	err = json.Unmarshal(resp.Result, &stickers)
543
544	return stickers, err
545}