1
0
Fork 0
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:
samhza 2021-06-18 02:32:11 -04:00 committed by GitHub
parent 94090b92ff
commit 37b8871c65
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 159 additions and 75 deletions

View file

@ -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
}

View file

@ -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)
}

View file

@ -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.

View file

@ -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"}
}
}

View file

@ -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)

View file

@ -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

View file

@ -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:

View file

@ -281,8 +281,6 @@ func (m MessageApplication) CreatedAt() time.Time {
return m.ID.Time()
}
//
// MessageReference is used in four situations:
//
// Crosspost messages

View file

@ -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 (

View file

@ -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 {