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