go-tg-screenshot-bot

Форк
0
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("<", "&lt;", ">", "&gt;", "&", "&amp;")
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
}
727

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.