1
0
Fork 0
mirror of https://github.com/diamondburned/arikawa.git synced 2024-09-17 15:38:46 +00:00
arikawa/api/interaction.go
diamondburned 96b8e2b8e5
api: Revert "Add FollowUpInteraction"
This reverts commit 2aaa2002d8.

The initial goal of this addition is to be used for interaction
followups, but the interaction event already comes with the application
ID.

The addition was also not done properly, and no invalidation on event is
done.
2022-08-20 16:15:50 -07:00

330 lines
11 KiB
Go

package api
import (
"mime/multipart"
"strconv"
"github.com/pkg/errors"
"github.com/diamondburned/arikawa/v3/discord"
"github.com/diamondburned/arikawa/v3/utils/json/option"
"github.com/diamondburned/arikawa/v3/utils/sendpart"
)
var EndpointInteractions = Endpoint + "interactions/"
type InteractionResponseType uint
// https://discord.com/developers/docs/interactions/slash-commands#interaction-response-object-interaction-callback-type
const (
PongInteraction InteractionResponseType = iota + 1
_
_
MessageInteractionWithSource
DeferredMessageInteractionWithSource
DeferredMessageUpdate
UpdateMessage
AutocompleteResult
ModalResponse
)
// InteractionResponseFlags implements flags for an
// InteractionApplicationCommandCallbackData.
//
// Deprecated: use discord.MessageFlags instead.
type InteractionResponseFlags = discord.MessageFlags
// EphemeralMessage specifies whether the message is only visible to the user
// who invoked the Interaction.
//
// Deprecated: use discord.EphemeralMessage instead.
const EphemeralResponse InteractionResponseFlags = discord.EphemeralMessage
type InteractionResponse struct {
Type InteractionResponseType `json:"type"`
Data *InteractionResponseData `json:"data,omitempty"`
}
// NeedsMultipart returns true if the InteractionResponse has files.
func (resp InteractionResponse) NeedsMultipart() bool {
return resp.Data != nil && resp.Data.NeedsMultipart()
}
func (resp InteractionResponse) WriteMultipart(body *multipart.Writer) error {
return sendpart.Write(body, resp, resp.Data.Files)
}
// InteractionResponseData is InteractionApplicationCommandCallbackData in the
// official documentation.
type InteractionResponseData struct {
// Content are the message contents (up to 2000 characters).
//
// Required: one of content, file, embeds
Content option.NullableString `json:"content,omitempty"`
// TTS is true if this is a TTS message.
TTS bool `json:"tts,omitempty"`
// Embeds contains embedded rich content.
//
// Required: one of content, file, embeds
Embeds *[]discord.Embed `json:"embeds,omitempty"`
// Components is the list of components (such as buttons) to be attached to
// the message.
Components *discord.ContainerComponents `json:"components,omitempty"`
// AllowedMentions are the allowed mentions for the message.
AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"`
// Flags are the interaction application command callback data flags.
// Only SuppressEmbeds and EphemeralMessage may be set.
Flags discord.MessageFlags `json:"flags,omitempty"`
// Files represents a list of files to upload. This will not be
// JSON-encoded and will only be available through WriteMultipart.
Files []sendpart.File `json:"-"`
// Choices are the results to display on autocomplete interaction events.
//
// During all other events, this should not be provided.
Choices AutocompleteChoices `json:"choices"`
// CustomID used with the modal
CustomID option.NullableString `json:"custom_id,omitempty"`
// Title is the heading of the modal window
Title option.NullableString `json:"title,omitempty"`
}
// NeedsMultipart returns true if the InteractionResponseData has files.
func (d InteractionResponseData) NeedsMultipart() bool {
return len(d.Files) > 0
}
func (d InteractionResponseData) WriteMultipart(body *multipart.Writer) error {
return sendpart.Write(body, d, d.Files)
}
// AutocompleteChoices are the choices to send back to Discord when sending a
// ApplicationCommandAutocompleteResult interaction response.
//
// The following types implement this interface:
//
// - AutocompleteStringChoices
// - AutocompleteIntegerChoices
// - AutocompleteNumberChoices
//
type AutocompleteChoices interface {
choices()
}
// AutocompleteStringChoices are string choices to send back to Discord as
// autocomplete results.
type AutocompleteStringChoices []discord.StringChoice
func (c AutocompleteStringChoices) choices() {}
// AutocompleteIntegerChoices are integer choices to send back to Discord as
// autocomplete results.
type AutocompleteIntegerChoices []discord.IntegerChoice
func (c AutocompleteIntegerChoices) choices() {}
// AutocompleteNumberChoices are number choices to send back to Discord as
// autocomplete results.
type AutocompleteNumberChoices []discord.NumberChoice
func (c AutocompleteNumberChoices) choices() {}
// RespondInteraction responds to an incoming interaction. It is also known as
// an "interaction callback".
func (c *Client) RespondInteraction(
id discord.InteractionID, token string, resp InteractionResponse) error {
if resp.Data != nil {
switch resp.Type {
case MessageInteractionWithSource:
// A new message is being created, make sure none of the fields
// are null or empty.
if (resp.Data.Content == nil || resp.Data.Content.Val == "") &&
(resp.Data.Embeds == nil || len(*resp.Data.Embeds) == 0) &&
len(resp.Data.Files) == 0 {
return ErrEmptyMessage
}
case UpdateMessage:
// A component is being updated. We therefore don't know what
// fields are filled. The only thing we can check is if content,
// embeds and files are null.
if (resp.Data.Content != nil && !resp.Data.Content.Init) &&
(resp.Data.Embeds != nil && *resp.Data.Embeds == nil) && len(resp.Data.Files) == 0 {
return ErrEmptyMessage
}
}
if resp.Data.AllowedMentions != nil {
if err := resp.Data.AllowedMentions.Verify(); err != nil {
return errors.Wrap(err, "allowedMentions error")
}
}
if resp.Data.Embeds != nil {
sum := 0
for i, embed := range *resp.Data.Embeds {
if err := embed.Validate(); err != nil {
return errors.Wrap(err, "embed error at "+strconv.Itoa(i))
}
sum += embed.Length()
if sum > 6000 {
return &discord.OverboundError{Count: sum, Max: 6000, Thing: "sum of all text in embeds"}
}
(*resp.Data.Embeds)[i] = embed // embed.Validate changes fields
}
}
}
URL := EndpointInteractions + id.String() + "/" + token + "/callback"
return sendpart.POST(c.Client, resp, nil, URL)
}
// InteractionResponse returns the initial interaction response.
func (c *Client) InteractionResponse(
appID discord.AppID, token string) (*discord.Message, error) {
var m *discord.Message
return m, c.RequestJSON(
&m, "GET",
EndpointWebhooks+appID.String()+"/"+token+"/messages/@original")
}
type EditInteractionResponseData struct {
// Content are the new message contents (up to 2000 characters).
Content option.NullableString `json:"content,omitempty"`
// Embeds contains embedded rich content.
Embeds *[]discord.Embed `json:"embeds,omitempty"`
// Components contains the new components to attach.
Components *discord.ContainerComponents `json:"components,omitempty"`
// AllowedMentions are the allowed mentions for the message.
AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"`
// Attachments are the attached files to keep.
Attachments *[]discord.Attachment `json:"attachments,omitempty"`
// Files represents a list of files to upload. This will not be
// JSON-encoded and will only be available through WriteMultipart.
Files []sendpart.File `json:"-"`
}
// NeedsMultipart returns true if the SendMessageData has files.
func (data EditInteractionResponseData) NeedsMultipart() bool {
return len(data.Files) > 0
}
func (data EditInteractionResponseData) WriteMultipart(body *multipart.Writer) error {
return sendpart.Write(body, data, data.Files)
}
// EditInteractionResponse edits the initial Interaction response.
func (c *Client) EditInteractionResponse(
appID discord.AppID,
token string, data EditInteractionResponseData) (*discord.Message, error) {
if data.AllowedMentions != nil {
if err := data.AllowedMentions.Verify(); err != nil {
return nil, errors.Wrap(err, "allowedMentions error")
}
}
if data.Embeds != nil {
sum := 0
for i, e := range *data.Embeds {
if err := e.Validate(); err != nil {
return nil, errors.Wrap(err, "embed error")
}
sum += e.Length()
if sum > 6000 {
return nil, &discord.OverboundError{Count: sum, Max: 6000, Thing: "sum of text in embeds"}
}
(*data.Embeds)[i] = e // e.Validate changes fields
}
}
var msg *discord.Message
return msg, sendpart.PATCH(c.Client, data, &msg,
EndpointWebhooks+appID.String()+"/"+token+"/messages/@original")
}
// DeleteInteractionResponse deletes the initial interaction response.
func (c *Client) DeleteInteractionResponse(appID discord.AppID, token string) error {
return c.FastRequest("DELETE",
EndpointWebhooks+appID.String()+"/"+token+"/messages/@original")
}
// CreateInteractionFollowup creates a followup message for an interaction.
func (c *Client) CreateInteractionFollowup(
appID discord.AppID, token string, data InteractionResponseData) (*discord.Message, error) {
if (data.Content == nil || data.Content.Val == "") &&
(data.Embeds == nil || len(*data.Embeds) == 0) && len(data.Files) == 0 {
return nil, ErrEmptyMessage
}
if data.AllowedMentions != nil {
if err := data.AllowedMentions.Verify(); err != nil {
return nil, errors.Wrap(err, "allowedMentions error")
}
}
if data.Embeds != nil {
sum := 0
for i, embed := range *data.Embeds {
if err := embed.Validate(); err != nil {
return nil, errors.Wrap(err, "embed error at "+strconv.Itoa(i))
}
sum += embed.Length()
if sum > 6000 {
return nil, &discord.OverboundError{Count: sum, Max: 6000, Thing: "sum of all text in embeds"}
}
(*data.Embeds)[i] = embed // embed.Validate changes fields
}
}
var msg *discord.Message
return msg, sendpart.POST(
c.Client, data, &msg, EndpointWebhooks+appID.String()+"/"+token+"?")
}
func (c *Client) EditInteractionFollowup(
appID discord.AppID, messageID discord.MessageID,
token string, data EditInteractionResponseData) (*discord.Message, error) {
if data.AllowedMentions != nil {
if err := data.AllowedMentions.Verify(); err != nil {
return nil, errors.Wrap(err, "allowedMentions error")
}
}
if data.Embeds != nil {
sum := 0
for i, e := range *data.Embeds {
if err := e.Validate(); err != nil {
return nil, errors.Wrap(err, "embed error")
}
sum += e.Length()
if sum > 6000 {
return nil, &discord.OverboundError{Count: sum, Max: 6000, Thing: "sum of text in embeds"}
}
(*data.Embeds)[i] = e // e.Validate changes fields
}
}
var msg *discord.Message
return msg, sendpart.PATCH(c.Client, data, &msg,
EndpointWebhooks+appID.String()+"/"+token+"/messages/"+messageID.String())
}
// DeleteInteractionFollowup deletes a followup message for an interaction.
func (c *Client) DeleteInteractionFollowup(
appID discord.AppID, messageID discord.MessageID, token string) error {
return c.FastRequest("DELETE",
EndpointWebhooks+appID.String()+"/"+token+"/messages/"+messageID.String())
}