This repository has been archived by the owner on Aug 8, 2021. It is now read-only.
forked from Bios-Marcel/discordgo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
interactions.go
302 lines (251 loc) · 9.34 KB
/
interactions.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
package discordgo
import (
"bytes"
"crypto/ed25519"
"encoding/hex"
"io"
"io/ioutil"
"net/http"
"time"
)
// InteractionDeadline is the time allowed to respond to an interaction.
const InteractionDeadline = time.Second * 3
// ApplicationCommand represents an application's slash command.
type ApplicationCommand struct {
ID string `json:"id"`
ApplicationID string `json:"application_id,omitempty"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Version string `json:"version,omitempty"`
Options []*ApplicationCommandOption `json:"options"`
}
// ApplicationCommandOptionType indicates the type of a slash command's option.
type ApplicationCommandOptionType uint8
// Application command option types.
const (
ApplicationCommandOptionSubCommand = ApplicationCommandOptionType(iota + 1)
ApplicationCommandOptionSubCommandGroup
ApplicationCommandOptionString
ApplicationCommandOptionInteger
ApplicationCommandOptionBoolean
ApplicationCommandOptionUser
ApplicationCommandOptionChannel
ApplicationCommandOptionRole
)
// ApplicationCommandOption represents an option/subcommand/subcommands group.
type ApplicationCommandOption struct {
Type ApplicationCommandOptionType `json:"type"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
// NOTE: This feature was on the API, but at some point developers decided to remove it.
// So I commented it, until it will be officially on the docs.
// Default bool `json:"default"`
Required bool `json:"required"`
Choices []*ApplicationCommandOptionChoice `json:"choices"`
Options []*ApplicationCommandOption `json:"options"`
}
// ApplicationCommandOptionChoice represents a slash command option choice.
type ApplicationCommandOptionChoice struct {
Name string `json:"name"`
Value interface{} `json:"value"`
}
// InteractionType indicates the type of an interaction event.
type InteractionType uint8
// Interaction types
const (
InteractionPing = InteractionType(iota + 1)
InteractionApplicationCommand
)
// Interaction represents an interaction event created via a slash command.
type Interaction struct {
ID string `json:"id"`
Type InteractionType `json:"type"`
Data ApplicationCommandInteractionData `json:"data"`
GuildID string `json:"guild_id"`
ChannelID string `json:"channel_id"`
// The member who invoked this interaction.
// NOTE: this field is only filled when the slash command was invoked in a guild;
// if it was invoked in a DM, the `User` field will be filled instead.
// Make sure to check for `nil` before using this field.
Member *Member `json:"member"`
// The user who invoked this interaction.
// NOTE: this field is only filled when the slash command was invoked in a DM;
// if it was invoked in a guild, the `Member` field will be filled instead.
// Make sure to check for `nil` before using this field.
User *User `json:"user"`
Token string `json:"token"`
Version int `json:"version"`
}
// ApplicationCommandInteractionData contains data received in an interaction event.
type ApplicationCommandInteractionData struct {
ID string `json:"id"`
Name string `json:"name"`
Options []*ApplicationCommandInteractionDataOption `json:"options"`
}
// ApplicationCommandInteractionDataOption represents an option of a slash command.
type ApplicationCommandInteractionDataOption struct {
Name string `json:"name"`
// NOTE: Contains the value specified by InteractionType.
Value interface{} `json:"value,omitempty"`
Options []*ApplicationCommandInteractionDataOption `json:"options,omitempty"`
}
// IntValue is a utility function for casting option value to integer
func (o ApplicationCommandInteractionDataOption) IntValue() int64 {
if v, ok := o.Value.(float64); ok {
return int64(v)
}
return 0
}
// UintValue is a utility function for casting option value to unsigned integer
func (o ApplicationCommandInteractionDataOption) UintValue() uint64 {
if v, ok := o.Value.(float64); ok {
return uint64(v)
}
return 0
}
// FloatValue is a utility function for casting option value to float
func (o ApplicationCommandInteractionDataOption) FloatValue() float64 {
if v, ok := o.Value.(float64); ok {
return v
}
return 0.0
}
// StringValue is a utility function for casting option value to string
func (o ApplicationCommandInteractionDataOption) StringValue() string {
if v, ok := o.Value.(string); ok {
return v
}
return ""
}
// BoolValue is a utility function for casting option value to bool
func (o ApplicationCommandInteractionDataOption) BoolValue() bool {
if v, ok := o.Value.(bool); ok {
return v
}
return false
}
// ChannelValue is a utility function for casting option value to channel object.
// s : Session object, if not nil, function additionaly fetches all channel's data
func (o ApplicationCommandInteractionDataOption) ChannelValue(s *Session) *Channel {
chanID := o.StringValue()
if chanID == "" {
return nil
}
if s == nil {
return &Channel{ID: chanID}
}
ch, err := s.State.Channel(chanID)
if err != nil {
ch, err = s.Channel(chanID)
if err != nil {
return &Channel{ID: chanID}
}
}
return ch
}
// RoleValue is a utility function for casting option value to role object.
// s : Session object, if not nil, function additionaly fetches all role's data
func (o ApplicationCommandInteractionDataOption) RoleValue(s *Session, gID string) *Role {
roleID := o.StringValue()
if roleID == "" {
return nil
}
if s == nil || gID == "" {
return &Role{ID: roleID}
}
r, err := s.State.Role(roleID, gID)
if err != nil {
roles, err := s.GuildRoles(gID)
if err == nil {
for _, r = range roles {
if r.ID == roleID {
return r
}
}
}
return &Role{ID: roleID}
}
return r
}
// UserValue is a utility function for casting option value to user object.
// s : Session object, if not nil, function additionaly fetches all user's data
func (o ApplicationCommandInteractionDataOption) UserValue(s *Session) *User {
userID := o.StringValue()
if userID == "" {
return nil
}
if s == nil {
return &User{ID: userID}
}
u, err := s.User(userID)
if err != nil {
return &User{ID: userID}
}
return u
}
// InteractionResponseType is type of interaction response.
type InteractionResponseType uint8
// Interaction response types.
const (
// InteractionResponsePong is for ACK ping event.
InteractionResponsePong = InteractionResponseType(iota + 1)
// InteractionResponseAcknowledge is for ACK a command without sending a message, eating the user's input.
// NOTE: this type is being imminently deprecated, and **will be removed when this occurs.**
InteractionResponseAcknowledge
// InteractionResponseChannelMessage is for responding with a message, eating the user's input.
// NOTE: this type is being imminently deprecated, and **will be removed when this occurs.**
InteractionResponseChannelMessage
// InteractionResponseChannelMessageWithSource is for responding with a message, showing the user's input.
InteractionResponseChannelMessageWithSource
// InteractionResponseDeferredChannelMessageWithSource acknowledges that the event was received, and that a follow-up will come later.
// It was previously named InteractionResponseACKWithSource.
InteractionResponseDeferredChannelMessageWithSource
)
// InteractionResponse represents a response for an interaction event.
type InteractionResponse struct {
Type InteractionResponseType `json:"type,omitempty"`
Data *InteractionApplicationCommandResponseData `json:"data,omitempty"`
}
// InteractionApplicationCommandResponseData is response data for a slash command interaction.
type InteractionApplicationCommandResponseData struct {
TTS bool `json:"tts,omitempty"`
Content string `json:"content,omitempty"`
Embeds []*MessageEmbed `json:"embeds,omitempty"`
AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"`
// NOTE: Undocumented feature, be careful with it.
Flags uint64 `json:"flags,omitempty"`
}
// VerifyInteraction implements message verification of the discord interactions api
// signing algorithm, as documented here:
// https://discord.com/developers/docs/interactions/slash-commands#security-and-authorization
func VerifyInteraction(r *http.Request, key ed25519.PublicKey) bool {
var msg bytes.Buffer
signature := r.Header.Get("X-Signature-Ed25519")
if signature == "" {
return false
}
sig, err := hex.DecodeString(signature)
if err != nil {
return false
}
if len(sig) != ed25519.SignatureSize {
return false
}
timestamp := r.Header.Get("X-Signature-Timestamp")
if timestamp == "" {
return false
}
msg.WriteString(timestamp)
defer r.Body.Close()
var body bytes.Buffer
// at the end of the function, copy the original body back into the request
defer func() {
r.Body = ioutil.NopCloser(&body)
}()
// copy body into buffers
_, err = io.Copy(&msg, io.TeeReader(r.Body, &body))
if err != nil {
return false
}
return ed25519.Verify(key, msg.Bytes(), sig)
}