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