-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcommand.go
More file actions
227 lines (196 loc) · 5.71 KB
/
Copy pathcommand.go
File metadata and controls
227 lines (196 loc) · 5.71 KB
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
package strut
import (
"strings"
"github.com/disgoorg/disgo/discord"
)
// Kind is the set of invocation surfaces a command is exposed on.
type Kind uint8
const (
Slash Kind = 1 << iota
Prefix
UserMenu
MessageMenu
// EntryPoint is the command Discord shows for an Activity.
EntryPoint
)
// Has reports whether k includes every surface in want.
func (k Kind) Has(want Kind) bool { return k&want == want }
func (k Kind) String() string {
names := make([]string, 0, 4)
for _, s := range []struct {
bit Kind
name string
}{
{Slash, "Slash"},
{Prefix, "Prefix"},
{UserMenu, "UserMenu"},
{MessageMenu, "MessageMenu"},
{EntryPoint, "EntryPoint"},
} {
if k&s.bit != 0 {
names = append(names, s.name)
}
}
if len(names) == 0 {
return "Kind(0)"
}
return strings.Join(names, "|")
}
// Runner is the behaviour every command and subcommand supplies.
type Runner[D any] interface {
Run(*Event[D]) error
}
// Command is a top-level command. Subcommands only implement Runner: their
// name and description come from the parent's field tag, so nesting is
// described where it is declared.
type Command[D any] interface {
Runner[D]
Meta() Meta
}
// Optional interfaces. Each is detected once when the command is added and
// never looked up again at invoke time.
type (
// Allower gates a command in addition to Meta.Checks.
Allower[D any] interface {
Allow(*Event[D]) (bool, error)
}
// Validator checks one command's input beyond what Discord can express.
// It runs after Options.Validate, if both are set.
Validator interface {
Validate() error
}
// Cooldowner rate limits a command.
Cooldowner interface {
Cooldown() CooldownConfig
}
// ErrorHandler overrides Options.OnError for one command.
ErrorHandler[D any] interface {
OnError(*Error[D])
}
// Localizer supplies translations for the command itself. Option
// translations come from name.<locale> and desc.<locale> tags.
Localizer interface {
Localize() L10n
}
// Helper supplies help text computed at display time. strut never calls
// it; it is the shape a help command can assert against, so commands
// agree on one.
Helper[D any] interface {
HelpText(*Event[D]) string
}
// Parent supplies subcommands that are not known at compile time.
Parent[D any] interface {
Children() []Command[D]
}
)
// L10n maps a locale to a translated name and description.
type L10n map[discord.Locale]Translation
// Translation is a localized command name and description. Either may be empty.
type Translation struct {
Name string
Description string
}
// Check reports whether a command may run. Returning false without an error
// rejects silently; returning an error reports it through OnError.
type Check[D any] func(*Event[D]) (bool, error)
// Meta is a command's static description. Behaviour lives on the optional
// interfaces above rather than here.
type Meta struct {
Name string
Description string
Aliases []string // prefix only
Category string
Kinds Kind // zero means Slash
// Gating, all checked before a command runs.
GuildOnly bool
DMOnly bool
NSFWOnly bool
OwnersOnly bool
UserPerms discord.Permissions
BotPerms discord.Permissions
// DefaultMemberPermissions is sent to Discord. Nil leaves it unset, which
// differs from a zero value: zero hides the command from everyone.
DefaultMemberPermissions *discord.Permissions
// How replies to this command behave.
Ephemeral bool
// ReuseResponse edits the first response instead of sending a followup.
ReuseResponse bool
// BroadcastTyping shows typing for prefix commands, and defers for
// interactions, both of which buy time before the reply.
BroadcastTyping bool
// Prefix behaviour, all requiring PrefixOptions.EditTracker.
InvokeOnEdit bool
TrackDeletion bool
// Where Discord offers the command.
IntegrationTypes []discord.ApplicationIntegrationType
Contexts []discord.InteractionContextType
ContextMenuName string
// EntryPointHandler decides whether Discord launches the activity itself
// or hands the interaction to the bot. Only read for Kinds EntryPoint.
EntryPointHandler discord.EntryPointCommandHandlerType
// How the command appears in the built-in help.
HideInHelp bool
HelpText string
// SubcommandRequired rejects a bare prefix invocation of a command that
// has subcommands, instead of running the parent's own Run. Slash
// commands cannot be invoked bare, so it has no effect there.
SubcommandRequired bool
ManualCooldown bool
CustomData any
}
// mergeSub layers a subcommand's own Meta over its root's.
//
// Only gating and response settings are taken: a subcommand's name and
// description come from the field tag, and its surfaces from the root. Zero
// fields are left alone, so a subcommand can tighten what the root allows but
// not loosen it.
func mergeSub(root, sub Meta) Meta {
out := root
if sub.GuildOnly {
out.GuildOnly = true
}
if sub.DMOnly {
out.DMOnly = true
}
if sub.NSFWOnly {
out.NSFWOnly = true
}
if sub.OwnersOnly {
out.OwnersOnly = true
}
if sub.Ephemeral {
out.Ephemeral = true
}
if sub.ReuseResponse {
out.ReuseResponse = true
}
if sub.BroadcastTyping {
out.BroadcastTyping = true
}
if sub.ManualCooldown {
out.ManualCooldown = true
}
out.UserPerms |= sub.UserPerms
out.BotPerms |= sub.BotPerms
if sub.HelpText != "" {
out.HelpText = sub.HelpText
}
if sub.CustomData != nil {
out.CustomData = sub.CustomData
}
return out
}
// kinds returns Meta.Kinds with the Slash default applied.
func (m Meta) kinds() Kind {
if m.Kinds == 0 {
return Slash
}
return m.Kinds
}
// contextMenuName returns the label shown in Discord's context menu.
func (m Meta) contextMenuName() string {
if m.ContextMenuName != "" {
return m.ContextMenuName
}
return m.Name
}