all repos — telegram-bot-api @ 23ed97b145bdc331428d0e094fdc973627293a89

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