github.com/diamondburned/arikawa/v2@v2.1.0/api/send.go (about)

     1  package api
     2  
     3  import (
     4  	"mime/multipart"
     5  
     6  	"github.com/pkg/errors"
     7  
     8  	"github.com/diamondburned/arikawa/v2/discord"
     9  	"github.com/diamondburned/arikawa/v2/utils/json/option"
    10  	"github.com/diamondburned/arikawa/v2/utils/sendpart"
    11  )
    12  
    13  const AttachmentSpoilerPrefix = "SPOILER_"
    14  
    15  // AllowedMentions is a allowlist of mentions for a message.
    16  //
    17  // Allowlists
    18  //
    19  // Roles and Users are slices that act as allowlists for IDs that are allowed
    20  // to be mentioned. For example, if only 1 ID is provided in Users, then only
    21  // that ID will be parsed in the message. No other IDs will be. The same
    22  // example also applies for roles.
    23  //
    24  // If Parse is an empty slice and both Users and Roles are empty slices, then no
    25  // mentions will be parsed.
    26  //
    27  // Constraints
    28  //
    29  // If the Users slice is not empty, then Parse must not have AllowUserMention.
    30  // Likewise, if the Roles slice is not empty, then Parse must not have
    31  // AllowRoleMention. This is because everything provided in Parse will make
    32  // Discord parse it completely, meaning they would be mutually exclusive with
    33  // Roles and Users.
    34  //
    35  // https://discord.com/developers/docs/resources/channel#allowed-mentions-object
    36  type AllowedMentions struct {
    37  	// Parse is an array of allowed mention types to parse from the content.
    38  	Parse []AllowedMentionType `json:"parse"`
    39  	// Roles is an array of role_ids to mention (Max size of 100).
    40  	Roles []discord.RoleID `json:"roles,omitempty"`
    41  	// Users is an array of user_ids to mention (Max size of 100).
    42  	Users []discord.UserID `json:"users,omitempty"`
    43  	// RepliedUser is used specifically for inline replies to specify, whether
    44  	// to mention the author of the message you are replying to or not.
    45  	RepliedUser option.Bool `json:"replied_user,omitempty"`
    46  }
    47  
    48  // AllowedMentionType is a constant that tells Discord what is allowed to parse
    49  // from a message content. This can help prevent things such as an
    50  // unintentional @everyone mention.
    51  type AllowedMentionType string
    52  
    53  // https://discord.com/developers/docs/resources/channel#allowed-mentions-object-allowed-mention-types
    54  const (
    55  	// AllowRoleMention makes Discord parse roles in the content.
    56  	AllowRoleMention AllowedMentionType = "roles"
    57  	// AllowUserMention makes Discord parse user mentions in the content.
    58  	AllowUserMention AllowedMentionType = "users"
    59  	// AllowEveryoneMention makes Discord parse @everyone mentions.
    60  	AllowEveryoneMention AllowedMentionType = "everyone"
    61  )
    62  
    63  // Verify checks the AllowedMentions against constraints mentioned in
    64  // AllowedMentions' documentation. This will be called on SendMessageComplex.
    65  func (am AllowedMentions) Verify() error {
    66  	if len(am.Roles) > 100 {
    67  		return errors.Errorf("roles slice length %d is over 100", len(am.Roles))
    68  	}
    69  	if len(am.Users) > 100 {
    70  		return errors.Errorf("users slice length %d is over 100", len(am.Users))
    71  	}
    72  
    73  	for _, allowed := range am.Parse {
    74  		switch allowed {
    75  		case AllowRoleMention:
    76  			if len(am.Roles) > 0 {
    77  				return errors.New(`parse has AllowRoleMention and Roles slice is not empty`)
    78  			}
    79  		case AllowUserMention:
    80  			if len(am.Users) > 0 {
    81  				return errors.New(`parse has AllowUserMention and Users slice is not empty`)
    82  			}
    83  		}
    84  	}
    85  
    86  	return nil
    87  }
    88  
    89  // ErrEmptyMessage is returned if either a SendMessageData or an
    90  // ExecuteWebhookData has both an empty Content and no Embed(s).
    91  var ErrEmptyMessage = errors.New("message is empty")
    92  
    93  // SendMessageData is the full structure to send a new message to Discord with.
    94  type SendMessageData struct {
    95  	// Content are the message contents (up to 2000 characters).
    96  	Content string `json:"content,omitempty"`
    97  	// Nonce is a nonce that can be used for optimistic message sending.
    98  	Nonce string `json:"nonce,omitempty"`
    99  
   100  	// TTS is true if this is a TTS message.
   101  	TTS bool `json:"tts,omitempty"`
   102  	// Embed is embedded rich content.
   103  	Embed *discord.Embed `json:"embed,omitempty"`
   104  
   105  	// Files is the list of file attachments to be uploaded. To reference a file
   106  	// in an embed, use (sendpart.File).AttachmentURI().
   107  	Files []sendpart.File `json:"-"`
   108  
   109  	// AllowedMentions are the allowed mentions for a message.
   110  	AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"`
   111  	// Reference allows you to reference another message to create a reply. The
   112  	// referenced message must be from the same channel.
   113  	//
   114  	// Only MessageID is necessary. You may also include a channel_id and
   115  	// guild_id in the reference. However, they are not necessary, but will be
   116  	// validated if sent.
   117  	Reference *discord.MessageReference `json:"message_reference,omitempty"`
   118  }
   119  
   120  // NeedsMultipart returns true if the SendMessageData has files.
   121  func (data SendMessageData) NeedsMultipart() bool {
   122  	return len(data.Files) > 0
   123  }
   124  
   125  func (data SendMessageData) WriteMultipart(body *multipart.Writer) error {
   126  	return sendpart.Write(body, data, data.Files)
   127  }
   128  
   129  // SendMessageComplex posts a message to a guild text or DM channel. If
   130  // operating on a guild channel, this endpoint requires the SEND_MESSAGES
   131  // permission to be present on the current user. If the tts field is set to
   132  // true, the SEND_TTS_MESSAGES permission is required for the message to be
   133  // spoken. Returns a message object. Fires a Message Create Gateway event.
   134  //
   135  // The maximum request size when sending a message is 8MB.
   136  //
   137  // This endpoint supports requests with Content-Types of both application/json
   138  // and multipart/form-data. You must however use multipart/form-data when
   139  // uploading files. Note that when sending multipart/form-data requests the
   140  // embed field cannot be used, however you can pass a JSON encoded body as form
   141  // value for payload_json, where additional request parameters such as embed
   142  // can be set.
   143  //
   144  // Note that when sending application/json you must send at least one of
   145  // content or embed, and when sending multipart/form-data, you must send at
   146  // least one of content, embed or file. For a file attachment, the
   147  // Content-Disposition subpart header MUST contain a filename parameter.
   148  func (c *Client) SendMessageComplex(
   149  	channelID discord.ChannelID, data SendMessageData) (*discord.Message, error) {
   150  
   151  	if data.Content == "" && data.Embed == nil && len(data.Files) == 0 {
   152  		return nil, ErrEmptyMessage
   153  	}
   154  
   155  	if data.AllowedMentions != nil {
   156  		if err := data.AllowedMentions.Verify(); err != nil {
   157  			return nil, errors.Wrap(err, "allowedMentions error")
   158  		}
   159  	}
   160  
   161  	if data.Embed != nil {
   162  		if err := data.Embed.Validate(); err != nil {
   163  			return nil, errors.Wrap(err, "embed error")
   164  		}
   165  	}
   166  
   167  	var URL = EndpointChannels + channelID.String() + "/messages"
   168  	var msg *discord.Message
   169  	return msg, sendpart.POST(c.Client, data, &msg, URL)
   170  }