bot.go (18016B)
1 // Package tgbotapi has functions and types used for interacting with 2 // the Telegram Bot API. 3 package tgbotapi 4 5 import ( 6 "encoding/json" 7 "errors" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "mime/multipart" 12 "net/http" 13 "net/url" 14 "strings" 15 "time" 16 ) 17 18 // HTTPClient is the type needed for the bot to perform HTTP requests. 19 type HTTPClient interface { 20 Do(req *http.Request) (*http.Response, error) 21 } 22 23 // BotAPI allows you to interact with the Telegram Bot API. 24 type BotAPI struct { 25 Token string `json:"token"` 26 Debug bool `json:"debug"` 27 Buffer int `json:"buffer"` 28 29 Self User `json:"-"` 30 Client HTTPClient `json:"-"` 31 shutdownChannel chan interface{} 32 33 apiEndpoint string 34 } 35 36 // NewBotAPI creates a new BotAPI instance. 37 // 38 // It requires a token, provided by @BotFather on Telegram. 39 func NewBotAPI(token string) (*BotAPI, error) { 40 return NewBotAPIWithClient(token, APIEndpoint, &http.Client{}) 41 } 42 43 // NewBotAPIWithAPIEndpoint creates a new BotAPI instance 44 // and allows you to pass API endpoint. 45 // 46 // It requires a token, provided by @BotFather on Telegram and API endpoint. 47 func NewBotAPIWithAPIEndpoint(token, apiEndpoint string) (*BotAPI, error) { 48 return NewBotAPIWithClient(token, apiEndpoint, &http.Client{}) 49 } 50 51 // NewBotAPIWithClient creates a new BotAPI instance 52 // and allows you to pass a http.Client. 53 // 54 // It requires a token, provided by @BotFather on Telegram and API endpoint. 55 func NewBotAPIWithClient(token, apiEndpoint string, client HTTPClient) (*BotAPI, error) { 56 bot := &BotAPI{ 57 Token: token, 58 Client: client, 59 Buffer: 100, 60 shutdownChannel: make(chan interface{}), 61 62 apiEndpoint: apiEndpoint, 63 } 64 65 self, err := bot.GetMe() 66 if err != nil { 67 return nil, err 68 } 69 70 bot.Self = self 71 72 return bot, nil 73 } 74 75 // SetAPIEndpoint changes the Telegram Bot API endpoint used by the instance. 76 func (bot *BotAPI) SetAPIEndpoint(apiEndpoint string) { 77 bot.apiEndpoint = apiEndpoint 78 } 79 80 func buildParams(in Params) url.Values { 81 if in == nil { 82 return url.Values{} 83 } 84 85 out := url.Values{} 86 87 for key, value := range in { 88 out.Set(key, value) 89 } 90 91 return out 92 } 93 94 // MakeRequest makes a request to a specific endpoint with our token. 95 func (bot *BotAPI) MakeRequest(endpoint string, params Params) (*APIResponse, error) { 96 if bot.Debug { 97 log.Printf("Endpoint: %s, params: %v\n", endpoint, params) 98 } 99 100 method := fmt.Sprintf(bot.apiEndpoint, bot.Token, endpoint) 101 102 values := buildParams(params) 103 104 req, err := http.NewRequest("POST", method, strings.NewReader(values.Encode())) 105 if err != nil { 106 return &APIResponse{}, err 107 } 108 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 109 110 resp, err := bot.Client.Do(req) 111 if err != nil { 112 return nil, err 113 } 114 defer resp.Body.Close() 115 116 var apiResp APIResponse 117 bytes, err := bot.decodeAPIResponse(resp.Body, &apiResp) 118 if err != nil { 119 return &apiResp, err 120 } 121 122 if bot.Debug { 123 log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes)) 124 } 125 126 if !apiResp.Ok { 127 var parameters ResponseParameters 128 129 if apiResp.Parameters != nil { 130 parameters = *apiResp.Parameters 131 } 132 133 return &apiResp, &Error{ 134 Code: apiResp.ErrorCode, 135 Message: apiResp.Description, 136 ResponseParameters: parameters, 137 } 138 } 139 140 return &apiResp, nil 141 } 142 143 // decodeAPIResponse decode response and return slice of bytes if debug enabled. 144 // If debug disabled, just decode http.Response.Body stream to APIResponse struct 145 // for efficient memory usage 146 func (bot *BotAPI) decodeAPIResponse(responseBody io.Reader, resp *APIResponse) ([]byte, error) { 147 if !bot.Debug { 148 dec := json.NewDecoder(responseBody) 149 err := dec.Decode(resp) 150 return nil, err 151 } 152 153 // if debug, read response body 154 data, err := ioutil.ReadAll(responseBody) 155 if err != nil { 156 return nil, err 157 } 158 159 err = json.Unmarshal(data, resp) 160 if err != nil { 161 return nil, err 162 } 163 164 return data, nil 165 } 166 167 // UploadFiles makes a request to the API with files. 168 func (bot *BotAPI) UploadFiles(endpoint string, params Params, files []RequestFile) (*APIResponse, error) { 169 r, w := io.Pipe() 170 m := multipart.NewWriter(w) 171 172 // This code modified from the very helpful @HirbodBehnam 173 // https://github.com/go-telegram-bot-api/telegram-bot-api/issues/354#issuecomment-663856473 174 go func() { 175 defer w.Close() 176 defer m.Close() 177 178 for field, value := range params { 179 if err := m.WriteField(field, value); err != nil { 180 w.CloseWithError(err) 181 return 182 } 183 } 184 185 for _, file := range files { 186 if file.Data.NeedsUpload() { 187 name, reader, err := file.Data.UploadData() 188 if err != nil { 189 w.CloseWithError(err) 190 return 191 } 192 193 part, err := m.CreateFormFile(file.Name, name) 194 if err != nil { 195 w.CloseWithError(err) 196 return 197 } 198 199 if _, err := io.Copy(part, reader); err != nil { 200 w.CloseWithError(err) 201 return 202 } 203 204 if closer, ok := reader.(io.ReadCloser); ok { 205 if err = closer.Close(); err != nil { 206 w.CloseWithError(err) 207 return 208 } 209 } 210 } else { 211 value := file.Data.SendData() 212 213 if err := m.WriteField(file.Name, value); err != nil { 214 w.CloseWithError(err) 215 return 216 } 217 } 218 } 219 }() 220 221 if bot.Debug { 222 log.Printf("Endpoint: %s, params: %v, with %d files\n", endpoint, params, len(files)) 223 } 224 225 method := fmt.Sprintf(bot.apiEndpoint, bot.Token, endpoint) 226 227 req, err := http.NewRequest("POST", method, r) 228 if err != nil { 229 return nil, err 230 } 231 232 req.Header.Set("Content-Type", m.FormDataContentType()) 233 234 resp, err := bot.Client.Do(req) 235 if err != nil { 236 return nil, err 237 } 238 defer resp.Body.Close() 239 240 var apiResp APIResponse 241 bytes, err := bot.decodeAPIResponse(resp.Body, &apiResp) 242 if err != nil { 243 return &apiResp, err 244 } 245 246 if bot.Debug { 247 log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes)) 248 } 249 250 if !apiResp.Ok { 251 var parameters ResponseParameters 252 253 if apiResp.Parameters != nil { 254 parameters = *apiResp.Parameters 255 } 256 257 return &apiResp, &Error{ 258 Message: apiResp.Description, 259 ResponseParameters: parameters, 260 } 261 } 262 263 return &apiResp, nil 264 } 265 266 // GetFileDirectURL returns direct URL to file 267 // 268 // It requires the FileID. 269 func (bot *BotAPI) GetFileDirectURL(fileID string) (string, error) { 270 file, err := bot.GetFile(FileConfig{fileID}) 271 272 if err != nil { 273 return "", err 274 } 275 276 return file.Link(bot.Token), nil 277 } 278 279 // GetMe fetches the currently authenticated bot. 280 // 281 // This method is called upon creation to validate the token, 282 // and so you may get this data from BotAPI.Self without the need for 283 // another request. 284 func (bot *BotAPI) GetMe() (User, error) { 285 resp, err := bot.MakeRequest("getMe", nil) 286 if err != nil { 287 return User{}, err 288 } 289 290 var user User 291 err = json.Unmarshal(resp.Result, &user) 292 293 return user, err 294 } 295 296 // IsMessageToMe returns true if message directed to this bot. 297 // 298 // It requires the Message. 299 func (bot *BotAPI) IsMessageToMe(message Message) bool { 300 return strings.Contains(message.Text, "@"+bot.Self.UserName) 301 } 302 303 func hasFilesNeedingUpload(files []RequestFile) bool { 304 for _, file := range files { 305 if file.Data.NeedsUpload() { 306 return true 307 } 308 } 309 310 return false 311 } 312 313 // Request sends a Chattable to Telegram, and returns the APIResponse. 314 func (bot *BotAPI) Request(c Chattable) (*APIResponse, error) { 315 params, err := c.params() 316 if err != nil { 317 return nil, err 318 } 319 320 if t, ok := c.(Fileable); ok { 321 files := t.files() 322 323 // If we have files that need to be uploaded, we should delegate the 324 // request to UploadFile. 325 if hasFilesNeedingUpload(files) { 326 return bot.UploadFiles(t.method(), params, files) 327 } 328 329 // However, if there are no files to be uploaded, there's likely things 330 // that need to be turned into params instead. 331 for _, file := range files { 332 params[file.Name] = file.Data.SendData() 333 } 334 } 335 336 return bot.MakeRequest(c.method(), params) 337 } 338 339 // Send will send a Chattable item to Telegram and provides the 340 // returned Message. 341 func (bot *BotAPI) Send(c Chattable) (Message, error) { 342 resp, err := bot.Request(c) 343 if err != nil { 344 return Message{}, err 345 } 346 347 var message Message 348 err = json.Unmarshal(resp.Result, &message) 349 350 return message, err 351 } 352 353 // SendMediaGroup sends a media group and returns the resulting messages. 354 func (bot *BotAPI) SendMediaGroup(config MediaGroupConfig) ([]Message, error) { 355 resp, err := bot.Request(config) 356 if err != nil { 357 return nil, err 358 } 359 360 var messages []Message 361 err = json.Unmarshal(resp.Result, &messages) 362 363 return messages, err 364 } 365 366 // GetUserProfilePhotos gets a user's profile photos. 367 // 368 // It requires UserID. 369 // Offset and Limit are optional. 370 func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) { 371 resp, err := bot.Request(config) 372 if err != nil { 373 return UserProfilePhotos{}, err 374 } 375 376 var profilePhotos UserProfilePhotos 377 err = json.Unmarshal(resp.Result, &profilePhotos) 378 379 return profilePhotos, err 380 } 381 382 // GetFile returns a File which can download a file from Telegram. 383 // 384 // Requires FileID. 385 func (bot *BotAPI) GetFile(config FileConfig) (File, error) { 386 resp, err := bot.Request(config) 387 if err != nil { 388 return File{}, err 389 } 390 391 var file File 392 err = json.Unmarshal(resp.Result, &file) 393 394 return file, err 395 } 396 397 // GetUpdates fetches updates. 398 // If a WebHook is set, this will not return any data! 399 // 400 // Offset, Limit, Timeout, and AllowedUpdates are optional. 401 // To avoid stale items, set Offset to one higher than the previous item. 402 // Set Timeout to a large number to reduce requests, so you can get updates 403 // instantly instead of having to wait between requests. 404 func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) { 405 resp, err := bot.Request(config) 406 if err != nil { 407 return []Update{}, err 408 } 409 410 var updates []Update 411 err = json.Unmarshal(resp.Result, &updates) 412 413 return updates, err 414 } 415 416 // GetWebhookInfo allows you to fetch information about a webhook and if 417 // one currently is set, along with pending update count and error messages. 418 func (bot *BotAPI) GetWebhookInfo() (WebhookInfo, error) { 419 resp, err := bot.MakeRequest("getWebhookInfo", nil) 420 if err != nil { 421 return WebhookInfo{}, err 422 } 423 424 var info WebhookInfo 425 err = json.Unmarshal(resp.Result, &info) 426 427 return info, err 428 } 429 430 // GetUpdatesChan starts and returns a channel for getting updates. 431 func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) UpdatesChannel { 432 ch := make(chan Update, bot.Buffer) 433 434 go func() { 435 for { 436 select { 437 case <-bot.shutdownChannel: 438 close(ch) 439 return 440 default: 441 } 442 443 updates, err := bot.GetUpdates(config) 444 if err != nil { 445 log.Println(err) 446 log.Println("Failed to get updates, retrying in 3 seconds...") 447 time.Sleep(time.Second * 3) 448 449 continue 450 } 451 452 for _, update := range updates { 453 if update.UpdateID >= config.Offset { 454 config.Offset = update.UpdateID + 1 455 ch <- update 456 } 457 } 458 } 459 }() 460 461 return ch 462 } 463 464 // StopReceivingUpdates stops the go routine which receives updates 465 func (bot *BotAPI) StopReceivingUpdates() { 466 if bot.Debug { 467 log.Println("Stopping the update receiver routine...") 468 } 469 close(bot.shutdownChannel) 470 } 471 472 // ListenForWebhook registers a http handler for a webhook. 473 func (bot *BotAPI) ListenForWebhook(pattern string) UpdatesChannel { 474 ch := make(chan Update, bot.Buffer) 475 476 http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) { 477 update, err := bot.HandleUpdate(r) 478 if err != nil { 479 errMsg, _ := json.Marshal(map[string]string{"error": err.Error()}) 480 w.WriteHeader(http.StatusBadRequest) 481 w.Header().Set("Content-Type", "application/json") 482 _, _ = w.Write(errMsg) 483 return 484 } 485 486 ch <- *update 487 }) 488 489 return ch 490 } 491 492 // ListenForWebhookRespReqFormat registers a http handler for a single incoming webhook. 493 func (bot *BotAPI) ListenForWebhookRespReqFormat(w http.ResponseWriter, r *http.Request) UpdatesChannel { 494 ch := make(chan Update, bot.Buffer) 495 496 func(w http.ResponseWriter, r *http.Request) { 497 update, err := bot.HandleUpdate(r) 498 if err != nil { 499 errMsg, _ := json.Marshal(map[string]string{"error": err.Error()}) 500 w.WriteHeader(http.StatusBadRequest) 501 w.Header().Set("Content-Type", "application/json") 502 _, _ = w.Write(errMsg) 503 return 504 } 505 506 ch <- *update 507 close(ch) 508 }(w, r) 509 510 return ch 511 } 512 513 // HandleUpdate parses and returns update received via webhook 514 func (bot *BotAPI) HandleUpdate(r *http.Request) (*Update, error) { 515 if r.Method != http.MethodPost { 516 err := errors.New("wrong HTTP method required POST") 517 return nil, err 518 } 519 520 var update Update 521 err := json.NewDecoder(r.Body).Decode(&update) 522 if err != nil { 523 return nil, err 524 } 525 526 return &update, nil 527 } 528 529 // WriteToHTTPResponse writes the request to the HTTP ResponseWriter. 530 // 531 // It doesn't support uploading files. 532 // 533 // See https://core.telegram.org/bots/api#making-requests-when-getting-updates 534 // for details. 535 func WriteToHTTPResponse(w http.ResponseWriter, c Chattable) error { 536 params, err := c.params() 537 if err != nil { 538 return err 539 } 540 541 if t, ok := c.(Fileable); ok { 542 if hasFilesNeedingUpload(t.files()) { 543 return errors.New("unable to use http response to upload files") 544 } 545 } 546 547 values := buildParams(params) 548 values.Set("method", c.method()) 549 550 w.Header().Set("Content-Type", "application/x-www-form-urlencoded") 551 _, err = w.Write([]byte(values.Encode())) 552 return err 553 } 554 555 // GetChat gets information about a chat. 556 func (bot *BotAPI) GetChat(config ChatInfoConfig) (Chat, error) { 557 resp, err := bot.Request(config) 558 if err != nil { 559 return Chat{}, err 560 } 561 562 var chat Chat 563 err = json.Unmarshal(resp.Result, &chat) 564 565 return chat, err 566 } 567 568 // GetChatAdministrators gets a list of administrators in the chat. 569 // 570 // If none have been appointed, only the creator will be returned. 571 // Bots are not shown, even if they are an administrator. 572 func (bot *BotAPI) GetChatAdministrators(config ChatAdministratorsConfig) ([]ChatMember, error) { 573 resp, err := bot.Request(config) 574 if err != nil { 575 return []ChatMember{}, err 576 } 577 578 var members []ChatMember 579 err = json.Unmarshal(resp.Result, &members) 580 581 return members, err 582 } 583 584 // GetChatMembersCount gets the number of users in a chat. 585 func (bot *BotAPI) GetChatMembersCount(config ChatMemberCountConfig) (int, error) { 586 resp, err := bot.Request(config) 587 if err != nil { 588 return -1, err 589 } 590 591 var count int 592 err = json.Unmarshal(resp.Result, &count) 593 594 return count, err 595 } 596 597 // GetChatMember gets a specific chat member. 598 func (bot *BotAPI) GetChatMember(config GetChatMemberConfig) (ChatMember, error) { 599 resp, err := bot.Request(config) 600 if err != nil { 601 return ChatMember{}, err 602 } 603 604 var member ChatMember 605 err = json.Unmarshal(resp.Result, &member) 606 607 return member, err 608 } 609 610 // GetGameHighScores allows you to get the high scores for a game. 611 func (bot *BotAPI) GetGameHighScores(config GetGameHighScoresConfig) ([]GameHighScore, error) { 612 resp, err := bot.Request(config) 613 if err != nil { 614 return []GameHighScore{}, err 615 } 616 617 var highScores []GameHighScore 618 err = json.Unmarshal(resp.Result, &highScores) 619 620 return highScores, err 621 } 622 623 // GetInviteLink get InviteLink for a chat 624 func (bot *BotAPI) GetInviteLink(config ChatInviteLinkConfig) (string, error) { 625 resp, err := bot.Request(config) 626 if err != nil { 627 return "", err 628 } 629 630 var inviteLink string 631 err = json.Unmarshal(resp.Result, &inviteLink) 632 633 return inviteLink, err 634 } 635 636 // GetStickerSet returns a StickerSet. 637 func (bot *BotAPI) GetStickerSet(config GetStickerSetConfig) (StickerSet, error) { 638 resp, err := bot.Request(config) 639 if err != nil { 640 return StickerSet{}, err 641 } 642 643 var stickers StickerSet 644 err = json.Unmarshal(resp.Result, &stickers) 645 646 return stickers, err 647 } 648 649 // StopPoll stops a poll and returns the result. 650 func (bot *BotAPI) StopPoll(config StopPollConfig) (Poll, error) { 651 resp, err := bot.Request(config) 652 if err != nil { 653 return Poll{}, err 654 } 655 656 var poll Poll 657 err = json.Unmarshal(resp.Result, &poll) 658 659 return poll, err 660 } 661 662 // GetMyCommands gets the currently registered commands. 663 func (bot *BotAPI) GetMyCommands() ([]BotCommand, error) { 664 return bot.GetMyCommandsWithConfig(GetMyCommandsConfig{}) 665 } 666 667 // GetMyCommandsWithConfig gets the currently registered commands with a config. 668 func (bot *BotAPI) GetMyCommandsWithConfig(config GetMyCommandsConfig) ([]BotCommand, error) { 669 resp, err := bot.Request(config) 670 if err != nil { 671 return nil, err 672 } 673 674 var commands []BotCommand 675 err = json.Unmarshal(resp.Result, &commands) 676 677 return commands, err 678 } 679 680 // CopyMessage copy messages of any kind. The method is analogous to the method 681 // forwardMessage, but the copied message doesn't have a link to the original 682 // message. Returns the MessageID of the sent message on success. 683 func (bot *BotAPI) CopyMessage(config CopyMessageConfig) (MessageID, error) { 684 params, err := config.params() 685 if err != nil { 686 return MessageID{}, err 687 } 688 689 resp, err := bot.MakeRequest(config.method(), params) 690 if err != nil { 691 return MessageID{}, err 692 } 693 694 var messageID MessageID 695 err = json.Unmarshal(resp.Result, &messageID) 696 697 return messageID, err 698 } 699 700 // EscapeText takes an input text and escape Telegram markup symbols. 701 // In this way we can send a text without being afraid of having to escape the characters manually. 702 // Note that you don't have to include the formatting style in the input text, or it will be escaped too. 703 // If there is an error, an empty string will be returned. 704 // 705 // parseMode is the text formatting mode (ModeMarkdown, ModeMarkdownV2 or ModeHTML) 706 // text is the input string that will be escaped 707 func EscapeText(parseMode string, text string) string { 708 var replacer *strings.Replacer 709 710 if parseMode == ModeHTML { 711 replacer = strings.NewReplacer("<", "<", ">", ">", "&", "&") 712 } else if parseMode == ModeMarkdown { 713 replacer = strings.NewReplacer("_", "\\_", "*", "\\*", "`", "\\`", "[", "\\[") 714 } else if parseMode == ModeMarkdownV2 { 715 replacer = strings.NewReplacer( 716 "_", "\\_", "*", "\\*", "[", "\\[", "]", "\\]", "(", 717 "\\(", ")", "\\)", "~", "\\~", "`", "\\`", ">", "\\>", 718 "#", "\\#", "+", "\\+", "-", "\\-", "=", "\\=", "|", 719 "\\|", "{", "\\{", "}", "\\}", ".", "\\.", "!", "\\!", 720 ) 721 } else { 722 return "" 723 } 724 725 return replacer.Replace(text) 726 }