-
Notifications
You must be signed in to change notification settings - Fork 0
/
commands.go
446 lines (385 loc) · 13.2 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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
package main
import (
"fmt"
"log"
"github.com/bwmarrin/discordgo"
)
// RegisterCommands registers the bot's commands with Discord
func (bot *Bot) RegisterCommands() error {
commands := []*discordgo.ApplicationCommand{
{
Name: "claim",
Description: "Claims a username or user ID",
Type: discordgo.ChatApplicationCommand,
Options: []*discordgo.ApplicationCommandOption{
{
Name: "username",
Description: "The username to claim",
Type: discordgo.ApplicationCommandOptionString,
Required: true,
},
},
},
{
Name: "unclaim",
Description: "Removes your claim to an advent of code account",
Type: discordgo.ChatApplicationCommand,
Options: []*discordgo.ApplicationCommandOption{
{
Name: "member",
Description: "The _discord_ user to remove the claim from (Admin only)",
Type: discordgo.ApplicationCommandOptionUser,
Required: false,
},
},
},
{
Name: "stars",
Description: "Returns how many stars you have collected (debugging)",
Type: discordgo.ChatApplicationCommand,
Options: []*discordgo.ApplicationCommandOption{
{
Name: "member",
Description: "The _discord_ user to get the star count for",
Type: discordgo.ApplicationCommandOptionUser,
Required: false,
},
},
},
{
Name: "spoilers",
Description: "Gives you access to the spoiler channels (toggle)\n",
Type: discordgo.ChatApplicationCommand,
},
{
Name: "setup",
Description: "Sets up this channel for use as a spoiler channel for a given day (Admin only)",
Type: discordgo.ChatApplicationCommand,
Options: []*discordgo.ApplicationCommandOption{
{
Name: "day",
Description: "The day to set up this channel for",
Type: discordgo.ApplicationCommandOptionInteger,
Required: true,
},
},
},
{
Name: "source",
Description: "Returns the source code for the bot",
Type: discordgo.ChatApplicationCommand,
},
{
Name: "help",
Description: "Shows help",
Type: discordgo.ChatApplicationCommand,
},
}
commands, err := bot.session.ApplicationCommandBulkOverwrite(bot.session.State.User.ID, "", commands)
if err != nil {
return err
}
for _, command := range commands {
log.Printf("Registered command: %s", command.Name)
}
return nil
}
// AddHandlers adds the bot's discordgo handlers
func (bot *Bot) AddHandlers() {
bot.session.AddHandler(bot.onInteractionCreate)
}
func (bot *Bot) onInteractionCreate(session *discordgo.Session, interaction *discordgo.InteractionCreate) {
i := interaction.Interaction
if interaction.Type != discordgo.InteractionApplicationCommand {
return
}
switch interaction.ApplicationCommandData().Name {
case "claim":
bot.onClaim(i)
case "unclaim":
bot.onUnclaim(i)
case "stars":
bot.onStars(i)
case "spoilers":
bot.onSpoil(i)
case "setup":
bot.onSetup(i)
case "source":
log.Printf("Source code requested by @%s", interaction.Member.User.Username)
bot.respondToInteraction(i, "https://github.com/Alextopher/aoc-bot", false)
case "help":
log.Printf("Help requested by @%s", interaction.Member.User.Username)
msg := "Help:\n"
msg += "- `/claim <username>`: Claims a username by Advent of Code name (or ID)\n"
msg += "- `/unclaim`: Removes your claim to an advent of code account\n"
msg += "- `/unclaim <member>`: Removes another user's claim to an advent of code account (Admin only)\n"
msg += "- `/stars`: Returns how many stars you have collected (debugging)\n"
msg += "- `/spoilers`: Gives you access to the spoiler channels (toggle)\n"
msg += "- `/source`: links my source code\n"
msg += "- `/help`: Shows this help message"
bot.respondToInteraction(i, msg, false)
}
}
func (bot *Bot) onClaim(interaction *discordgo.Interaction) {
deferred := bot.deferInteraction(interaction, true)
username := interaction.ApplicationCommandData().Options[0].StringValue()
log.Printf("Trying to claim username %s for @%s", username, interaction.Member.User.Username)
// Get the guild state
guildState, ok := bot.states[interaction.GuildID]
if !ok {
deferred.finalize("Error 1: This guild is not configured, yet.")
return
}
// Try to claim the user by name
err := guildState.ClaimName(interaction.Member.User.ID, username)
if err == ErrDoesNotExist {
// If the user doesn't exist, try to claim by ID
err = guildState.ClaimID(interaction.Member.User.ID, username)
}
if err == ErrDoesNotExist {
// If the user still doesn't exist, try to find close names to help the user out
closeNames, err := guildState.CloseNames(username)
if err != nil {
// Report that their name is invalid
deferred.finalize("Error 2: I couldn't find that user.")
return
}
// Build the error message
message := "Error 3: I couldn't find that user. Did you mean one of these?\n"
for _, name := range closeNames {
message += fmt.Sprintf("- '%s'\n", name)
}
deferred.finalize(message)
} else if err == ErrAlreadyClaimed {
// Check if this user just tried to re-claim themselves
aocID, ok := guildState.db.GetAdventID(interaction.Member.User.ID)
if ok {
member, ok := guildState.GetLeaderboard().GetMemberByID(aocID)
if aocID == username || (ok && member.Name == username) {
// Report that this user just tried to re-claim themselves
deferred.finalize("You have already claimed this user :smile:")
return
}
}
// Report that the user has already been claimed
deferred.finalize("Error 4: This user has already been claimed, if you believe this is an error, please contact an administrator")
} else if err != nil {
// Report that something went wrong
deferred.finalize("Error 5: Something went wrong, please try again later.")
} else {
// Report that the user has been claimed
deferred.finalize("Success: You have claimed your Advent of Code user!")
}
guild, err := bot.session.State.Guild(interaction.GuildID)
if err != nil {
log.Println("Error (onClaim) getting guild: ", err)
return
}
// Update roles
err = bot.SyncMemberRoles(guild, interaction.Member)
if err != nil {
log.Println("Error (onClaim) syncing roles: ", err)
return
}
}
func (bot *Bot) onUnclaim(interaction *discordgo.Interaction) {
// Defer the interaction response
deferred := bot.deferInteraction(interaction, true)
// Get the optional user
user := interaction.Member.User
// `self` is true if the user is unclaiming themselves, false if they are unclaiming another user
self := true
if len(interaction.ApplicationCommandData().Options) > 0 {
// Verify that the caller is an admin
if !bot.IsAdmin(interaction.Member) {
deferred.finalize("Error 6: You must be an admin to remove another user's claim.")
return
}
user = interaction.ApplicationCommandData().Options[0].UserValue(bot.session)
self = false
}
log.Printf("Unclaim requested by @%s for @%s", interaction.Member.User.Username, user.Username)
// Get the guild state
guildState, ok := bot.states[interaction.GuildID]
if !ok {
deferred.finalize("Error 7: This guild is not configured, yet.")
return
}
// Try to unclaim the user
log.Println("Unclaiming user: ", user.ID)
err := guildState.Unclaim(user.ID)
if err == ErrDoesNotExist {
// Report that the discord user never claimed an Advent of Code user
if self {
deferred.finalize("Success?: You ever claimed an Advent of Code user.")
} else {
deferred.finalize("Success?: That user never claimed an Advent of Code user.")
}
} else if err != nil {
// Report that something went wrong
deferred.finalize("Error 8: Something went wrong, please try again later.")
} else {
// Report that the user has been unclaimed
if self {
deferred.finalize("Success: You have unclaimed your Advent of Code user!")
} else {
deferred.finalize("Success: That user has been unclaimed!")
}
}
guild, err := bot.session.State.Guild(interaction.GuildID)
if err != nil {
log.Println("Error (onUnclaim) getting guild: ", err)
return
}
// Convert User to Member
member, err := bot.session.GuildMember(guild.ID, user.ID)
if err != nil {
log.Println("Error (onUnclaim) getting guild member: ", err)
return
}
bot.RemoveAllRoles(guild, member)
}
func (bot *Bot) onStars(interaction *discordgo.Interaction) {
// Defer the interaction response
deferred := bot.deferInteraction(interaction, true)
user := interaction.Member.User
self := true
if len(interaction.ApplicationCommandData().Options) > 0 {
user = interaction.ApplicationCommandData().Options[0].UserValue(bot.session)
self = false
}
log.Printf("Star count for @%s requested by @%s", user, interaction.Member.User.Username)
guildState, ok := bot.states[interaction.GuildID]
if !ok {
deferred.finalize("Error 9: This guild is not configured, yet.")
return
}
guildState.UpdateLeaderboard()
id, ok := guildState.db.GetAdventID(user.ID)
if !ok {
deferred.finalize("Error 10: You haven't ran `/claim` yet.")
return
}
aocMember, ok := guildState.GetLeaderboard().GetMemberByID(id)
if !ok {
deferred.finalize("Error 11: Something odd happened here, did you quit the leaderboard?")
return
}
// Sync the user's roles
guild, err := bot.session.State.Guild(interaction.GuildID)
if err != nil {
deferred.finalize("Error 12: Something went wrong, please try again later.")
return
}
// Success!
if self {
msg := fmt.Sprintf("You have collected **%d** stars!", aocMember.Stars)
deferred.finalize(msg)
} else {
msg := fmt.Sprintf("They have collected **%d** stars!", aocMember.Stars)
deferred.finalize(msg)
}
member, err := bot.session.GuildMember(guild.ID, user.ID)
if err != nil {
log.Println("Error (onStars) getting guild member: ", err)
return
}
log.Printf("Syncing roles for @%s", member.User.Username)
err = bot.SyncMemberRoles(guild, member)
if err != nil {
log.Println("Error (onStars) syncing roles: ", err)
return
}
}
func (bot *Bot) onSpoil(interaction *discordgo.Interaction) {
deferred := bot.deferInteraction(interaction, true)
log.Printf("Toggling spoilers has been requested by @%s", interaction.Member.User.Username)
guild, err := bot.session.State.Guild(interaction.GuildID)
if err != nil {
log.Println("Error (onStars) getting guild: ", err)
deferred.finalize("Error 13: Something went wrong, please try again later.")
return
}
added, err := bot.ToggleRole(guild, interaction.Member, "Spoiler")
if err != nil {
log.Println("Error (onStars) toggling role: ", err)
deferred.finalize("Error 14: Something went wrong, please try again later.")
} else if added {
deferred.finalize("Success: You have been given access to the spoiler channels!")
} else {
deferred.finalize("Success: You have been removed from the spoiler channels!")
}
}
func (bot *Bot) onSetup(interaction *discordgo.Interaction) {
// Defer the interaction response
deferred := bot.deferInteraction(interaction, true)
log.Printf("Setup requested by @%s", interaction.Member.User.Username)
// Verify that the caller is an admin
if !bot.IsAdmin(interaction.Member) {
deferred.finalize("Error 15: You must be an admin to set up a channel.")
return
}
day := interaction.ApplicationCommandData().Options[0].IntValue()
guild, err := bot.session.Guild(interaction.GuildID)
if err != nil {
log.Println("Error (onSetup) getting guild: ", err)
deferred.finalize("Error 16: Something went wrong, please try again later.")
return
}
// Set up this channel for this day
err = bot.SetupChannel(guild, interaction.ChannelID, day)
if err != nil {
deferred.finalize("Error 17: Something went wrong, please try again later.")
} else {
deferred.finalize("Success: This channel has been set up for spoilers!")
}
}
// DeferredInteraction is a small wrapper around an interaction that allows for deferring the response
type DeferredInteraction struct {
interaction *discordgo.Interaction
bot *Bot
}
func (bot *Bot) deferInteraction(i *discordgo.Interaction, isEphemeral bool) DeferredInteraction {
flags := discordgo.MessageFlags(0)
if isEphemeral {
flags = discordgo.MessageFlagsEphemeral
}
err := bot.session.InteractionRespond(i, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseDeferredChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Flags: flags,
},
})
if err != nil {
log.Println("deferInteraction failed while responding to interaction: ", err)
}
return DeferredInteraction{
interaction: i,
bot: bot,
}
}
func (di *DeferredInteraction) finalize(content string) {
_, err := di.bot.session.InteractionResponseEdit(di.interaction, &discordgo.WebhookEdit{
Content: &content,
})
if err != nil {
log.Println("finalize failed while responding to interaction: ", err)
}
}
// respondToInteraction responds to a new interaction that hasn't been deferred
func (bot *Bot) respondToInteraction(i *discordgo.Interaction, content string, isEphemeral bool) {
flags := discordgo.MessageFlags(0)
if isEphemeral {
flags = discordgo.MessageFlagsEphemeral
}
err := bot.session.InteractionRespond(i, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: content,
Flags: flags,
},
})
if err != nil {
log.Println("respondToInteraction failed while responding to interaction: ", err)
}
}