-
Notifications
You must be signed in to change notification settings - Fork 118
/
bot.go
127 lines (109 loc) · 3.8 KB
/
bot.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package gotgbot
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"time"
)
//go:generate go run ./scripts/generate
var (
ErrNilBotClient = errors.New("nil BotClient")
ErrInvalidTokenFormat = errors.New("invalid token format")
)
// Bot is the default Bot struct used to send and receive messages to the telegram API.
type Bot struct {
// Token stores the bot's secret token obtained from t.me/BotFather, and used to interact with telegram's API.
Token string
// The bot's User info, as returned by Bot.GetMe. Populated when created through the NewBot method.
User
// The bot client to use to make requests
BotClient
}
// BotOpts declares all optional parameters for the NewBot function.
type BotOpts struct {
// BotClient allows for passing in custom configurations of BotClient, such as handling extra errors or providing
// metrics.
BotClient BotClient
// DisableTokenCheck can be used to disable the token validity check.
// Useful when running in time-constrained environments where the startup time should be minimised, and where the
// token can be assumed to be valid (eg lambdas).
// Warning: Disabling the token check will mean that the Bot.User struct will no longer be populated.
DisableTokenCheck bool
// Request opts to use for checking token validity with Bot.GetMe. Can be slow - a high timeout (eg 10s) is
// recommended.
RequestOpts *RequestOpts
}
// NewBot returns a new Bot struct populated with the necessary defaults.
func NewBot(token string, opts *BotOpts) (*Bot, error) {
botClient := BotClient(&BaseBotClient{
Client: http.Client{},
UseTestEnvironment: false,
DefaultRequestOpts: nil,
})
// Large timeout on the initial GetMe request as this can sometimes be slow.
getMeReqOpts := &RequestOpts{
Timeout: 10 * time.Second,
}
checkTokenValidity := true
if opts != nil {
if opts.BotClient != nil {
botClient = opts.BotClient
}
if opts.RequestOpts != nil {
getMeReqOpts = opts.RequestOpts
}
checkTokenValidity = !opts.DisableTokenCheck
}
b := Bot{
Token: token,
BotClient: botClient,
}
if checkTokenValidity {
// Get bot info. This serves two purposes:
// 1. Check token is valid.
// 2. Populate the bot struct "User" field.
botUser, err := b.GetMe(&GetMeOpts{RequestOpts: getMeReqOpts})
if err != nil {
return nil, fmt.Errorf("failed to check bot token: %w", err)
}
b.User = *botUser
} else {
// If token checks are disabled, we populate the bot's ID from the token.
split := strings.Split(token, ":")
if len(split) != 2 {
return nil, fmt.Errorf("%w: expected '123:abcd', got %s", ErrInvalidTokenFormat, token)
}
id, err := strconv.ParseInt(split[0], 10, 64)
if err != nil {
return nil, fmt.Errorf("failed to parse bot ID from token: %w", err)
}
b.User = User{
Id: id,
IsBot: true,
// We mark these fields as missing so we can know why they're not available
FirstName: "<missing>",
Username: "<missing>",
}
}
return &b, nil
}
// UseMiddleware allows you to wrap the existing bot client to enhance functionality
//
// Deprecated: Instead of using middlewares, consider implementing the BotClient interface.
func (bot *Bot) UseMiddleware(mw func(client BotClient) BotClient) *Bot {
bot.BotClient = mw(bot.BotClient)
return bot
}
func (bot *Bot) Request(method string, params map[string]string, data map[string]FileReader, opts *RequestOpts) (json.RawMessage, error) {
return bot.RequestWithContext(context.Background(), method, params, data, opts)
}
func (bot *Bot) RequestWithContext(ctx context.Context, method string, params map[string]string, data map[string]FileReader, opts *RequestOpts) (json.RawMessage, error) {
if bot.BotClient == nil {
return nil, ErrNilBotClient
}
return bot.BotClient.RequestWithContext(ctx, bot.Token, method, params, data, opts)
}