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}