all repos — telegram-bot-api @ 655c3a4137072ec3d30bf69378c601b1fc44770f

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	"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}