mirror of
https://github.com/diamondburned/arikawa.git
synced 2025-11-23 13:14:16 +00:00
api: Update sending/editing messages for v9 (#230)
api.{Send,Edit}MessageData and their equivalents in package api/webhook
have been updated to add some fields added in Discord API v9.
(webhook.Client).EditMessage now also returns a message, because that
endpoint returns a message on success.
This commit is contained in:
parent
94090b92ff
commit
37b8871c65
|
|
@ -64,7 +64,7 @@ func (bot *Bot) GuildInfo(m *gateway.MessageCreateEvent) (string, error) {
|
|||
// Repeat tells the bot to wait for the user's response, then repeat what they
|
||||
// said.
|
||||
func (bot *Bot) Repeat(m *gateway.MessageCreateEvent) (string, error) {
|
||||
_, err := bot.Ctx.SendMessage(m.ChannelID, "What do you want me to say?", nil)
|
||||
_, err := bot.Ctx.SendMessage(m.ChannelID, "What do you want me to say?")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ func TestReactions(t *testing.T) {
|
|||
msg := fmt.Sprintf("This is a message sent at %v.", time.Now())
|
||||
|
||||
// Send a new message.
|
||||
m, err := client.SendMessage(cfg.ChannelID, msg, nil)
|
||||
m, err := client.SendMessage(cfg.ChannelID, msg)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to send message:", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"mime/multipart"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/diamondburned/arikawa/v3/discord"
|
||||
"github.com/diamondburned/arikawa/v3/internal/intmath"
|
||||
"github.com/diamondburned/arikawa/v3/utils/httputil"
|
||||
"github.com/diamondburned/arikawa/v3/utils/json/option"
|
||||
"github.com/diamondburned/arikawa/v3/utils/sendpart"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -220,17 +224,17 @@ func (c *Client) SendTextReply(
|
|||
})
|
||||
}
|
||||
|
||||
// SendEmbed posts an Embed to a guild text or DM channel.
|
||||
// SendEmbeds sends embeds to a guild text or DM channel.
|
||||
//
|
||||
// If operating on a guild channel, this endpoint requires the SEND_MESSAGES
|
||||
// permission to be present on the current user.
|
||||
//
|
||||
// Fires a Message Create Gateway event.
|
||||
func (c *Client) SendEmbed(
|
||||
channelID discord.ChannelID, e discord.Embed) (*discord.Message, error) {
|
||||
func (c *Client) SendEmbeds(
|
||||
channelID discord.ChannelID, e ...discord.Embed) (*discord.Message, error) {
|
||||
|
||||
return c.SendMessageComplex(channelID, SendMessageData{
|
||||
Embed: &e,
|
||||
Embeds: e,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -246,7 +250,7 @@ func (c *Client) SendEmbedReply(
|
|||
referenceID discord.MessageID) (*discord.Message, error) {
|
||||
|
||||
return c.SendMessageComplex(channelID, SendMessageData{
|
||||
Embed: &e,
|
||||
Embeds: []discord.Embed{e},
|
||||
Reference: &discord.MessageReference{MessageID: referenceID},
|
||||
})
|
||||
}
|
||||
|
|
@ -258,12 +262,12 @@ func (c *Client) SendEmbedReply(
|
|||
//
|
||||
// Fires a Message Create Gateway event.
|
||||
func (c *Client) SendMessage(
|
||||
channelID discord.ChannelID, content string, embed *discord.Embed) (*discord.Message, error) {
|
||||
|
||||
return c.SendMessageComplex(channelID, SendMessageData{
|
||||
channelID discord.ChannelID, content string, embeds ...discord.Embed) (*discord.Message, error) {
|
||||
data := SendMessageData{
|
||||
Content: content,
|
||||
Embed: embed,
|
||||
})
|
||||
Embeds: embeds,
|
||||
}
|
||||
return c.SendMessageComplex(channelID, data)
|
||||
}
|
||||
|
||||
// SendMessageReply posts a reply to a message ID in a guild text or DM channel.
|
||||
|
|
@ -277,29 +281,44 @@ func (c *Client) SendMessageReply(
|
|||
content string,
|
||||
embed *discord.Embed,
|
||||
referenceID discord.MessageID) (*discord.Message, error) {
|
||||
|
||||
return c.SendMessageComplex(channelID, SendMessageData{
|
||||
data := SendMessageData{
|
||||
Content: content,
|
||||
Embed: embed,
|
||||
Reference: &discord.MessageReference{MessageID: referenceID},
|
||||
})
|
||||
}
|
||||
if embed != nil {
|
||||
data.Embeds = []discord.Embed{*embed}
|
||||
}
|
||||
return c.SendMessageComplex(channelID, data)
|
||||
}
|
||||
|
||||
// https://discord.com/developers/docs/resources/channel#edit-message-json-params
|
||||
// https://discord.com/developers/docs/resources/channel#edit-message
|
||||
type EditMessageData struct {
|
||||
// Content is the new message contents (up to 2000 characters).
|
||||
Content option.NullableString `json:"content,omitempty"`
|
||||
// Embed contains embedded rich content.
|
||||
Embed *discord.Embed `json:"embed,omitempty"`
|
||||
// Embeds contains embedded rich content.
|
||||
Embeds *[]discord.Embed `json:"embeds,omitempty"`
|
||||
// Components contains the new components to attach.
|
||||
Components *[]discord.Component `json:"components,omitempty"`
|
||||
// AllowedMentions are the allowed mentions for a message.
|
||||
AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"`
|
||||
// Attachments are the attached files to keep
|
||||
Attachments *[]discord.Attachment `json:"attachments,omitempty"`
|
||||
// Flags edits the flags of a message (only SUPPRESS_EMBEDS can currently
|
||||
// be set/unset)
|
||||
//
|
||||
// This field is nullable.
|
||||
Flags *discord.MessageFlags `json:"flags,omitempty"`
|
||||
|
||||
Files []sendpart.File `json:"-"`
|
||||
}
|
||||
|
||||
// NeedsMultipart returns true if the SendMessageData has files.
|
||||
func (data EditMessageData) NeedsMultipart() bool {
|
||||
return len(data.Files) > 0
|
||||
}
|
||||
|
||||
func (data EditMessageData) WriteMultipart(body *multipart.Writer) error {
|
||||
return sendpart.Write(body, data, data.Files)
|
||||
}
|
||||
|
||||
// EditText edits the contents of a previously sent message. For more
|
||||
|
|
@ -312,13 +331,13 @@ func (c *Client) EditText(
|
|||
})
|
||||
}
|
||||
|
||||
// EditEmbed edits the embed of a previously sent message. For more
|
||||
// EditEmbeds edits the embed of a previously sent message. For more
|
||||
// documentation, refer to EditMessageComplex.
|
||||
func (c *Client) EditEmbed(
|
||||
channelID discord.ChannelID, messageID discord.MessageID, embed discord.Embed) (*discord.Message, error) {
|
||||
func (c *Client) EditEmbeds(
|
||||
channelID discord.ChannelID, messageID discord.MessageID, embeds ...discord.Embed) (*discord.Message, error) {
|
||||
|
||||
return c.EditMessageComplex(channelID, messageID, EditMessageData{
|
||||
Embed: &embed,
|
||||
Embeds: &embeds,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -330,7 +349,9 @@ func (c *Client) EditMessage(
|
|||
|
||||
var data = EditMessageData{
|
||||
Content: option.NewNullableString(content),
|
||||
Embed: embed,
|
||||
}
|
||||
if embed != nil {
|
||||
data.Embeds = &[]discord.Embed{*embed}
|
||||
}
|
||||
if suppressEmbeds {
|
||||
v := discord.SuppressEmbeds
|
||||
|
|
@ -352,25 +373,27 @@ func (c *Client) EditMessage(
|
|||
// Fires a Message Update Gateway event.
|
||||
func (c *Client) EditMessageComplex(
|
||||
channelID discord.ChannelID, messageID discord.MessageID, data EditMessageData) (*discord.Message, error) {
|
||||
|
||||
if data.AllowedMentions != nil {
|
||||
if err := data.AllowedMentions.Verify(); err != nil {
|
||||
return nil, errors.Wrap(err, "allowedMentions error")
|
||||
}
|
||||
}
|
||||
|
||||
if data.Embed != nil {
|
||||
if err := data.Embed.Validate(); err != nil {
|
||||
return nil, errors.Wrap(err, "embed 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{sum, 6000, "sum of all text in embeds"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var msg *discord.Message
|
||||
return msg, c.RequestJSON(
|
||||
&msg, "PATCH",
|
||||
EndpointChannels+channelID.String()+"/messages/"+messageID.String(),
|
||||
httputil.WithJSONBody(data),
|
||||
)
|
||||
return msg, sendpart.PATCH(c.Client, data, &msg,
|
||||
EndpointChannels+channelID.String()+"/messages/"+messageID.String())
|
||||
}
|
||||
|
||||
// CrosspostMessage crossposts a message in a news channel to following channels.
|
||||
|
|
|
|||
19
api/send.go
19
api/send.go
|
|
@ -2,6 +2,7 @@ package api
|
|||
|
||||
import (
|
||||
"mime/multipart"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
|
|
@ -87,7 +88,7 @@ func (am AllowedMentions) Verify() error {
|
|||
}
|
||||
|
||||
// ErrEmptyMessage is returned if either a SendMessageData or an
|
||||
// ExecuteWebhookData has both an empty Content and no Embed(s).
|
||||
// ExecuteWebhookData is missing content, embeds, and files.
|
||||
var ErrEmptyMessage = errors.New("message is empty")
|
||||
|
||||
// SendMessageData is the full structure to send a new message to Discord with.
|
||||
|
|
@ -100,7 +101,7 @@ type SendMessageData struct {
|
|||
// TTS is true if this is a TTS message.
|
||||
TTS bool `json:"tts,omitempty"`
|
||||
// Embed is embedded rich content.
|
||||
Embed *discord.Embed `json:"embed,omitempty"`
|
||||
Embeds []discord.Embed `json:"embeds,omitempty"`
|
||||
|
||||
// Files is the list of file attachments to be uploaded. To reference a file
|
||||
// in an embed, use (sendpart.File).AttachmentURI().
|
||||
|
|
@ -150,8 +151,7 @@ func (data SendMessageData) WriteMultipart(body *multipart.Writer) error {
|
|||
// Content-Disposition subpart header MUST contain a filename parameter.
|
||||
func (c *Client) SendMessageComplex(
|
||||
channelID discord.ChannelID, data SendMessageData) (*discord.Message, error) {
|
||||
|
||||
if data.Content == "" && data.Embed == nil && len(data.Files) == 0 {
|
||||
if data.Content == "" && len(data.Embeds) == 0 && len(data.Files) == 0 {
|
||||
return nil, ErrEmptyMessage
|
||||
}
|
||||
|
||||
|
|
@ -161,9 +161,14 @@ func (c *Client) SendMessageComplex(
|
|||
}
|
||||
}
|
||||
|
||||
if data.Embed != nil {
|
||||
if err := data.Embed.Validate(); err != nil {
|
||||
return nil, errors.Wrap(err, "embed error")
|
||||
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{sum, 6000, "sum of all text in embeds"}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -101,10 +101,7 @@ func TestSendMessage(t *testing.T) {
|
|||
}
|
||||
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
var empty = SendMessageData{
|
||||
Content: "",
|
||||
Embed: nil,
|
||||
}
|
||||
var empty SendMessageData
|
||||
|
||||
if err := send(empty); err != ErrEmptyMessage {
|
||||
t.Fatal("Unexpected error:", err)
|
||||
|
|
@ -136,10 +133,10 @@ func TestSendMessage(t *testing.T) {
|
|||
|
||||
t.Run("invalid embed", func(t *testing.T) {
|
||||
var data = SendMessageData{
|
||||
Embed: &discord.Embed{
|
||||
Embeds: []discord.Embed{{
|
||||
// max 256
|
||||
Title: spaces(257),
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
err := send(data)
|
||||
|
|
|
|||
|
|
@ -141,6 +141,10 @@ type ExecuteData struct {
|
|||
// 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.Component `json:"components,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:"-"`
|
||||
|
|
@ -185,10 +189,15 @@ func (c *Client) execute(data ExecuteData, wait bool) (*discord.Message, error)
|
|||
}
|
||||
}
|
||||
|
||||
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{sum, 6000, "sum of all text in embeds"}
|
||||
}
|
||||
}
|
||||
|
||||
var param url.Values
|
||||
|
|
@ -208,21 +217,53 @@ func (c *Client) execute(data ExecuteData, wait bool) (*discord.Message, error)
|
|||
}
|
||||
|
||||
// https://discord.com/developers/docs/resources/webhook#edit-webhook-message-jsonform-params
|
||||
|
||||
type EditMessageData struct {
|
||||
// Content are the message contents. They may be up to 2000 characters
|
||||
// characters long.
|
||||
// Content is the new message contents (up to 2000 characters).
|
||||
Content option.NullableString `json:"content,omitempty"`
|
||||
// Embeds is an array of up to 10 discord.Embeds.
|
||||
// Embeds contains embedded rich content.
|
||||
Embeds *[]discord.Embed `json:"embeds,omitempty"`
|
||||
// AllowedMentions are the AllowedMentions for the message.
|
||||
// Components contains the new components to attach.
|
||||
Components *[]discord.Component `json:"components,omitempty"`
|
||||
// AllowedMentions are the allowed mentions for a message.
|
||||
AllowedMentions *api.AllowedMentions `json:"allowed_mentions,omitempty"`
|
||||
// Attachments are the attached files to keep
|
||||
Attachments *[]discord.Attachment `json:"attachments,omitempty"`
|
||||
|
||||
Files []sendpart.File `json:"-"`
|
||||
}
|
||||
|
||||
// EditMessage edits a previously-sent webhook message from the same webhook.
|
||||
func (c *Client) EditMessage(messageID discord.MessageID, data EditMessageData) error {
|
||||
return c.FastRequest("PATCH",
|
||||
api.EndpointWebhooks+c.ID.String()+"/"+c.Token+"/messages/"+messageID.String(),
|
||||
httputil.WithJSONBody(data))
|
||||
func (c *Client) EditMessage(messageID discord.MessageID, data EditMessageData) (*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 _, 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{sum, 6000, "sum of text in embeds"}
|
||||
}
|
||||
}
|
||||
}
|
||||
var msg *discord.Message
|
||||
return msg, sendpart.PATCH(c.Client, data, &msg,
|
||||
api.EndpointWebhooks+c.ID.String()+"/"+c.Token+"/messages/"+messageID.String())
|
||||
}
|
||||
|
||||
// NeedsMultipart returns true if the SendMessageData has files.
|
||||
func (data EditMessageData) NeedsMultipart() bool {
|
||||
return len(data.Files) > 0
|
||||
}
|
||||
|
||||
func (data EditMessageData) WriteMultipart(body *multipart.Writer) error {
|
||||
return sendpart.Write(body, data, data.Files)
|
||||
}
|
||||
|
||||
// DeleteMessage deletes a message that was previously created by the same
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ func (ctx *Context) callMessageCreate(
|
|||
case string:
|
||||
data.Content = v
|
||||
case *discord.Embed:
|
||||
data.Embed = v
|
||||
data.Embeds = []discord.Embed{*v}
|
||||
case *api.SendMessageData:
|
||||
data = *v
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -281,8 +281,6 @@ func (m MessageApplication) CreatedAt() time.Time {
|
|||
return m.ID.Time()
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
// MessageReference is used in four situations:
|
||||
//
|
||||
// Crosspost messages
|
||||
|
|
|
|||
|
|
@ -91,24 +91,16 @@ func (e *Embed) Validate() error {
|
|||
return &OverboundError{len(e.Fields), 25, "fields"}
|
||||
}
|
||||
|
||||
var sum = 0 +
|
||||
len(e.Title) +
|
||||
len(e.Description)
|
||||
|
||||
if e.Footer != nil {
|
||||
if len(e.Footer.Text) > 2048 {
|
||||
return &OverboundError{len(e.Footer.Text), 2048, "footer text"}
|
||||
}
|
||||
|
||||
sum += len(e.Footer.Text)
|
||||
}
|
||||
|
||||
if e.Author != nil {
|
||||
if len(e.Author.Name) > 256 {
|
||||
return &OverboundError{len(e.Author.Name), 256, "author name"}
|
||||
}
|
||||
|
||||
sum += len(e.Author.Name)
|
||||
}
|
||||
|
||||
for i, field := range e.Fields {
|
||||
|
|
@ -121,17 +113,32 @@ func (e *Embed) Validate() error {
|
|||
return &OverboundError{len(field.Value), 1024,
|
||||
fmt.Sprintf("field %d value", i)}
|
||||
}
|
||||
|
||||
sum += len(field.Name) + len(field.Value)
|
||||
}
|
||||
|
||||
if sum > 6000 {
|
||||
if sum := e.Length(); sum > 6000 {
|
||||
return &OverboundError{sum, 6000, "sum of all characters"}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Length returns the sum of the lengths of all text in the embed.
|
||||
func (e Embed) Length() int {
|
||||
var sum = 0 +
|
||||
len(e.Title) +
|
||||
len(e.Description)
|
||||
if e.Footer != nil {
|
||||
sum += len(e.Footer.Text)
|
||||
}
|
||||
if e.Author != nil {
|
||||
sum += len(e.Author.Name)
|
||||
}
|
||||
for _, field := range e.Fields {
|
||||
sum += len(field.Name) + len(field.Value)
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
type EmbedType string
|
||||
|
||||
const (
|
||||
|
|
|
|||
|
|
@ -37,16 +37,15 @@ type DataMultipartWriter interface {
|
|||
httputil.MultipartWriter
|
||||
}
|
||||
|
||||
// POST sends a POST request using client to the given URL and unmarshal the
|
||||
// body into v if it's not nil. It will only send using multipart if files is
|
||||
// true.
|
||||
func POST(c *httputil.Client, data DataMultipartWriter, v interface{}, url string) error {
|
||||
// Do sends an HTTP request using client to the given URL and unmarshals the
|
||||
// body into v if it's not nil. It will only send using multipart if needed.
|
||||
func Do(c *httputil.Client, method string, data DataMultipartWriter, v interface{}, url string) error {
|
||||
if !data.NeedsMultipart() {
|
||||
// No files, so no need for streaming.
|
||||
return c.RequestJSON(v, "POST", url, httputil.WithJSONBody(data))
|
||||
return c.RequestJSON(v, method, url, httputil.WithJSONBody(data))
|
||||
}
|
||||
|
||||
resp, err := c.MeanwhileMultipart(data, "POST", url)
|
||||
resp, err := c.MeanwhileMultipart(data, method, url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -61,6 +60,20 @@ func POST(c *httputil.Client, data DataMultipartWriter, v interface{}, url strin
|
|||
return json.DecodeStream(body, v)
|
||||
}
|
||||
|
||||
// PATCH sends a PATCH request using client to the given URL and unmarshals the
|
||||
// body into v if it's not nil. It will only send using multipart if needed.
|
||||
// It is equivalent to calling Do with "POST"
|
||||
func POST(c *httputil.Client, data DataMultipartWriter, v interface{}, url string) error {
|
||||
return Do(c, "POST", data, v, url)
|
||||
}
|
||||
|
||||
// PATCH sends a PATCH request using client to the given URL and unmarshals the
|
||||
// body into v if it's not nil. It will only send using multipart if needed.
|
||||
// It is equivalent to calling Do with "PATCH"
|
||||
func PATCH(c *httputil.Client, data DataMultipartWriter, v interface{}, url string) error {
|
||||
return Do(c, "PATCH", data, v, url)
|
||||
}
|
||||
|
||||
// Write writes the item into payload_json and the list of files into the
|
||||
// multipart writer. Write does not close the body.
|
||||
func Write(body *multipart.Writer, item interface{}, files []File) error {
|
||||
|
|
|
|||
Loading…
Reference in a new issue