-
Notifications
You must be signed in to change notification settings - Fork 0
/
commands.go
230 lines (197 loc) · 6.47 KB
/
commands.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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
package finch
import (
"log"
"regexp"
"strconv"
"strings"
"github.com/getsentry/raven-go"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
var commands []*CommandState
var inline InlineCommand
var callback CallbackCommand
// RegisterCommand adds a command to the bot.
func RegisterCommand(cmd Command) {
commands = append(commands, NewCommandState(cmd))
}
// SetInline sets the Inline Query handler.
func SetInline(handler InlineCommand) {
inline = handler
}
// SetCallback sets the Callback Query handler. If this is not set,
// it will look through commands that have set waiting.
func SetCallback(handler CallbackCommand) {
callback = handler
}
// SimpleCommand generates a command regex and matches it against a message.
//
// The trigger is the command without the slash,
// and the message is the text to check it against.
func SimpleCommand(trigger, message string) bool {
// regex to match command, any arguments, and optionally bot name
return regexp.MustCompile("^/(" + trigger + ")(@\\w+)?( .+)?$").MatchString(message)
}
// SimpleArgCommand generates a command regex and matches it against a message,
// requiring a certain number of parameters.
//
// The trigger is the command without the slash, args is number of arguments,
// and the message is the text to check it against.
func SimpleArgCommand(trigger string, args int, message string) bool {
// regex to match command, any arguments, and optionally bot name
matches := regexp.MustCompile("^/(" + trigger + ")(@\\w+)?( .+)?$").FindStringSubmatch(message)
// if we don't have enough regex matches for all those items, return false
if len(matches) < 4 {
return false
}
// get the number of arguments (space seperated)
msgArgs := len(strings.Split(strings.Trim(matches[3], " "), " "))
// return if the number of args we got matches the number expected
return args == msgArgs
}
// commandRouter is run for every update, and runs the correct commands.
func (f *Finch) commandRouter(update tgbotapi.Update) {
defer func() {
if r := recover(); r != nil {
err, ok := r.(error)
if ok && sentryEnabled {
raven.CaptureError(err, nil)
} else {
log.Println(r)
}
}
}()
// if we've gotten an inline query, handle it
if update.InlineQuery != nil {
// if we do not have a handler, return
if f.Inline == nil {
log.Println("Got inline query, but no handler is set!")
return
}
// execute inline handler function
if err := f.Inline.Execute(f, *update.InlineQuery); err != nil {
// no way to show inline error to user, so log it
if sentryEnabled {
raven.CaptureError(err, nil)
}
log.Printf("Error processing inline query:\n%+v\n", err)
}
return
}
// check if we have a callback query
if update.CallbackQuery != nil {
if callback != nil {
callback.Execute(f, *update.CallbackQuery)
return
}
for _, command := range f.Commands {
// check if the command is waiting for input
if command.IsWaiting(update.CallbackQuery.From.ID) || command.Command.ShouldExecuteCallback(*update.CallbackQuery) {
if err := command.Command.ExecuteCallback(*update.CallbackQuery); err != nil {
f.commandError(command.Command.Help().Name, *update.CallbackQuery.Message, err)
}
}
}
return
}
// nothing past here can handle this!
if update.Message == nil {
return
}
// loop to check for any high priority commands
for _, command := range f.Commands {
// if it isn't a high priority command, ignore it
if !command.Command.IsHighPriority(*update.Message) {
continue
}
// if we shouldn't execute this command, ignore it
if !command.Command.ShouldExecute(*update.Message) {
continue
}
// execute the command
if err := command.Command.Execute(*update.Message); err != nil {
// some kind of error happened, send a message to sender
f.commandError(command.Command.Help().Name, *update.Message, err)
}
}
// now we can run all others
for _, command := range f.Commands {
// check if we're waiting for some text
if command.IsWaiting(update.Message.From.ID) {
// execute the waiting command
if err := command.Command.ExecuteWaiting(*update.Message); err != nil {
// some kind of error happened, send a message to sender
f.commandError(command.Command.Help().Name, *update.Message, err)
}
// command has already dealt with this, contine to next
continue
}
// we already did high priority commands, so skip now
if command.Command.IsHighPriority(*update.Message) {
continue
}
// check if we should execute this command
if command.Command.ShouldExecute(*update.Message) {
// execute the command
if err := command.Command.Execute(*update.Message); err != nil {
// some kind of error happened, send a message to sender
f.commandError(command.Command.Help().Name, *update.Message, err)
}
}
}
}
// called to init all commands
func (f *Finch) commandInit() {
// for each command
for _, command := range f.Commands {
// run the command init function
err := command.Command.Init(command, f)
if err != nil {
// it failed, show the error
log.Printf("Error starting command %s: %s\n", command.Command.Help().Name, err.Error())
if sentryEnabled {
raven.CaptureError(err, map[string]string{"command": command.Command.Help().Name})
}
} else {
// command started successfully
log.Printf("Started command %s!", command.Command.Help().Name)
}
}
}
// handle some kind of error
func (f *Finch) commandError(commandName string, message tgbotapi.Message, err error) {
var msg tgbotapi.MessageConfig
// check if in Debug mode
if f.API.Debug {
// we're debugging, safe to send the actual error message
msg = tgbotapi.NewMessage(message.Chat.ID, err.Error())
} else {
// Attempt to load a custom error message, if one exists.
var text string
if m, ok := f.Config.Get("error_message").(string); ok {
text = m
} else {
text = "An error occured processing your command!"
}
// production mode, just show a generic error message
msg = tgbotapi.NewMessage(message.Chat.ID, text)
// log the error
log.Println("Error processing command: " + err.Error())
}
msg.ReplyToMessageID = message.MessageID
if sentryEnabled {
raven.CaptureError(err, map[string]string{
"command": commandName,
}, &raven.User{
ID: strconv.FormatInt(message.From.ID, 10),
Username: message.From.UserName,
})
}
// send the error message
_, err = f.API.Send(msg)
if err != nil {
log.Printf("An error happened processing an error!\n%s\n", err.Error())
if sentryEnabled {
raven.CaptureError(err, nil)
}
}
}