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

     1  package api
     2  
     3  import (
     4  	"github.com/pkg/errors"
     5  
     6  	"github.com/diamondburned/arikawa/v2/discord"
     7  	"github.com/diamondburned/arikawa/v2/internal/intmath"
     8  	"github.com/diamondburned/arikawa/v2/utils/httputil"
     9  	"github.com/diamondburned/arikawa/v2/utils/json/option"
    10  )
    11  
    12  const (
    13  	// the limit of max messages per request, as imposed by Discord
    14  	maxMessageFetchLimit = 100
    15  	// maxMessageDeleteLimit is the limit of max message that can be deleted
    16  	// per bulk delete request, as imposed by Discord.
    17  	maxMessageDeleteLimit = 100
    18  )
    19  
    20  // Messages returns a slice filled with the most recent messages sent in the
    21  // channel with the passed ID. The method automatically paginates until it
    22  // reaches the passed limit, or, if the limit is set to 0, has fetched all
    23  // messages in the channel.
    24  //
    25  // As the underlying endpoint is capped at a maximum of 100 messages per
    26  // request, at maximum a total of limit/100 rounded up requests will be made,
    27  // although they may be less, if no more messages are available.
    28  //
    29  // When fetching the messages, those with the highest ID, will be fetched
    30  // first.
    31  // The returned slice will be sorted from latest to oldest.
    32  func (c *Client) Messages(channelID discord.ChannelID, limit uint) ([]discord.Message, error) {
    33  	// Since before is 0 it will be omitted by the http lib, which in turn
    34  	// will lead discord to send us the most recent messages without having to
    35  	// specify a Snowflake.
    36  	return c.MessagesBefore(channelID, 0, limit)
    37  }
    38  
    39  // MessagesAround returns messages around the ID, with a limit of 100.
    40  func (c *Client) MessagesAround(
    41  	channelID discord.ChannelID, around discord.MessageID, limit uint) ([]discord.Message, error) {
    42  
    43  	return c.messagesRange(channelID, 0, 0, around, limit)
    44  }
    45  
    46  // MessagesBefore returns a slice filled with the messages sent in the channel
    47  // with the passed id. The method automatically paginates until it reaches the
    48  // passed limit, or, if the limit is set to 0, has fetched all messages in the
    49  // channel with an id smaller than before.
    50  //
    51  // As the underlying endpoint has a maximum of 100 messages per request, at
    52  // maximum a total of limit/100 rounded up requests will be made, although they
    53  // may be less, if no more messages are available.
    54  //
    55  // The returned slice will be sorted from latest to oldest.
    56  func (c *Client) MessagesBefore(
    57  	channelID discord.ChannelID, before discord.MessageID, limit uint) ([]discord.Message, error) {
    58  
    59  	msgs := make([]discord.Message, 0, limit)
    60  
    61  	fetch := uint(maxMessageFetchLimit)
    62  
    63  	// Check if we are truly fetching unlimited messages to avoid confusion
    64  	// later on, if the limit reaches 0.
    65  	unlimited := limit == 0
    66  
    67  	for limit > 0 || unlimited {
    68  		if limit > 0 {
    69  			// Only fetch as much as we need. Since limit gradually decreases,
    70  			// we only need to fetch intmath.Min(fetch, limit).
    71  			fetch = uint(intmath.Min(maxMessageFetchLimit, int(limit)))
    72  			limit -= maxMessageFetchLimit
    73  		}
    74  
    75  		m, err := c.messagesRange(channelID, before, 0, 0, fetch)
    76  		if err != nil {
    77  			return msgs, err
    78  		}
    79  		// Append the older messages into the list of newer messages.
    80  		msgs = append(msgs, m...)
    81  
    82  		if len(m) < maxMessageFetchLimit {
    83  			break
    84  		}
    85  
    86  		before = m[len(m)-1].ID
    87  	}
    88  
    89  	if len(msgs) == 0 {
    90  		return nil, nil
    91  	}
    92  
    93  	return msgs, nil
    94  }
    95  
    96  // MessagesAfter returns a slice filled with the messages sent in the channel
    97  // with the passed ID. The method automatically paginates until it reaches the
    98  // passed limit, or, if the limit is set to 0, has fetched all messages in the
    99  // channel with an id higher than after.
   100  //
   101  // As the underlying endpoint has a maximum of 100 messages per request, at
   102  // maximum a total of limit/100 rounded up requests will be made, although they
   103  // may be less, if no more messages are available.
   104  //
   105  // The returned slice will be sorted from latest to oldest.
   106  func (c *Client) MessagesAfter(
   107  	channelID discord.ChannelID, after discord.MessageID, limit uint) ([]discord.Message, error) {
   108  
   109  	// 0 is uint's zero value and will lead to the after param getting omitted,
   110  	// which in turn will lead to the most recent messages being returned.
   111  	// Setting this to 1 will prevent that.
   112  	if after == 0 {
   113  		after = 1
   114  	}
   115  
   116  	var msgs []discord.Message
   117  
   118  	fetch := uint(maxMessageFetchLimit)
   119  
   120  	// Check if we are truly fetching unlimited messages to avoid confusion
   121  	// later on, if the limit reaches 0.
   122  	unlimited := limit == 0
   123  
   124  	for limit > 0 || unlimited {
   125  		if limit > 0 {
   126  			// Only fetch as much as we need. Since limit gradually decreases,
   127  			// we only need to fetch intmath.Min(fetch, limit).
   128  			fetch = uint(intmath.Min(maxMessageFetchLimit, int(limit)))
   129  			limit -= maxMessageFetchLimit
   130  		}
   131  
   132  		m, err := c.messagesRange(channelID, 0, after, 0, fetch)
   133  		if err != nil {
   134  			return msgs, err
   135  		}
   136  		// Prepend the older messages into the newly-fetched messages list.
   137  		msgs = append(m, msgs...)
   138  
   139  		if len(m) < maxMessageFetchLimit {
   140  			break
   141  		}
   142  
   143  		after = m[0].ID
   144  	}
   145  
   146  	if len(msgs) == 0 {
   147  		return nil, nil
   148  	}
   149  
   150  	return msgs, nil
   151  }
   152  
   153  func (c *Client) messagesRange(
   154  	channelID discord.ChannelID, before, after, around discord.MessageID, limit uint) ([]discord.Message, error) {
   155  
   156  	switch {
   157  	case limit == 0:
   158  		limit = 50
   159  	case limit > 100:
   160  		limit = 100
   161  	}
   162  
   163  	var param struct {
   164  		Before discord.MessageID `schema:"before,omitempty"`
   165  		After  discord.MessageID `schema:"after,omitempty"`
   166  		Around discord.MessageID `schema:"around,omitempty"`
   167  
   168  		Limit uint `schema:"limit"`
   169  	}
   170  
   171  	param.Before = before
   172  	param.After = after
   173  	param.Around = around
   174  	param.Limit = limit
   175  
   176  	var msgs []discord.Message
   177  	return msgs, c.RequestJSON(
   178  		&msgs, "GET",
   179  		EndpointChannels+channelID.String()+"/messages",
   180  		httputil.WithSchema(c, param),
   181  	)
   182  }
   183  
   184  // Message returns a specific message in the channel.
   185  //
   186  // If operating on a guild channel, this endpoint requires the
   187  // READ_MESSAGE_HISTORY permission to be present on the current user.
   188  func (c *Client) Message(channelID discord.ChannelID, messageID discord.MessageID) (*discord.Message, error) {
   189  	var msg *discord.Message
   190  	return msg, c.RequestJSON(&msg, "GET",
   191  		EndpointChannels+channelID.String()+"/messages/"+messageID.String())
   192  }
   193  
   194  // SendText posts a text-only message to a guild text or DM channel.
   195  //
   196  // If operating on a guild channel, this endpoint requires the SEND_MESSAGES
   197  // permission to be present on the current user.
   198  //
   199  // Fires a Message Create Gateway event.
   200  func (c *Client) SendText(channelID discord.ChannelID, content string) (*discord.Message, error) {
   201  	return c.SendMessageComplex(channelID, SendMessageData{
   202  		Content: content,
   203  	})
   204  }
   205  
   206  // SendTextReply posts a text-only reply to a message ID in a guild text or DM channel
   207  //
   208  // If operating on a guild channel, this endpoint requires the SEND_MESSAGES
   209  // permission to be present on the current user.
   210  //
   211  // Fires a Message Create Gateway event.
   212  func (c *Client) SendTextReply(
   213  	channelID discord.ChannelID,
   214  	content string,
   215  	referenceID discord.MessageID) (*discord.Message, error) {
   216  
   217  	return c.SendMessageComplex(channelID, SendMessageData{
   218  		Content:   content,
   219  		Reference: &discord.MessageReference{MessageID: referenceID},
   220  	})
   221  }
   222  
   223  // SendEmbed posts an Embed to a guild text or DM channel.
   224  //
   225  // If operating on a guild channel, this endpoint requires the SEND_MESSAGES
   226  // permission to be present on the current user.
   227  //
   228  // Fires a Message Create Gateway event.
   229  func (c *Client) SendEmbed(
   230  	channelID discord.ChannelID, e discord.Embed) (*discord.Message, error) {
   231  
   232  	return c.SendMessageComplex(channelID, SendMessageData{
   233  		Embed: &e,
   234  	})
   235  }
   236  
   237  // SendEmbedReply posts an Embed reply to a message ID in a guild text or DM channel.
   238  //
   239  // If operating on a guild channel, this endpoint requires the SEND_MESSAGES
   240  // permission to be present on the current user.
   241  //
   242  // Fires a Message Create Gateway event.
   243  func (c *Client) SendEmbedReply(
   244  	channelID discord.ChannelID,
   245  	e discord.Embed,
   246  	referenceID discord.MessageID) (*discord.Message, error) {
   247  
   248  	return c.SendMessageComplex(channelID, SendMessageData{
   249  		Embed:     &e,
   250  		Reference: &discord.MessageReference{MessageID: referenceID},
   251  	})
   252  }
   253  
   254  // SendMessage posts a message to a guild text or DM channel.
   255  //
   256  // If operating on a guild channel, this endpoint requires the SEND_MESSAGES
   257  // permission to be present on the current user.
   258  //
   259  // Fires a Message Create Gateway event.
   260  func (c *Client) SendMessage(
   261  	channelID discord.ChannelID, content string, embed *discord.Embed) (*discord.Message, error) {
   262  
   263  	return c.SendMessageComplex(channelID, SendMessageData{
   264  		Content: content,
   265  		Embed:   embed,
   266  	})
   267  }
   268  
   269  // SendMessageReply posts a reply to a message ID in a guild text or DM channel.
   270  //
   271  // If operating on a guild channel, this endpoint requires the SEND_MESSAGES
   272  // permission to be present on the current user.
   273  //
   274  // Fires a Message Create Gateway event.
   275  func (c *Client) SendMessageReply(
   276  	channelID discord.ChannelID,
   277  	content string,
   278  	embed *discord.Embed,
   279  	referenceID discord.MessageID) (*discord.Message, error) {
   280  
   281  	return c.SendMessageComplex(channelID, SendMessageData{
   282  		Content:   content,
   283  		Embed:     embed,
   284  		Reference: &discord.MessageReference{MessageID: referenceID},
   285  	})
   286  }
   287  
   288  // https://discord.com/developers/docs/resources/channel#edit-message-json-params
   289  type EditMessageData struct {
   290  	// Content is the new message contents (up to 2000 characters).
   291  	Content option.NullableString `json:"content,omitempty"`
   292  	// Embed contains embedded rich content.
   293  	Embed *discord.Embed `json:"embed,omitempty"`
   294  	// AllowedMentions are the allowed mentions for a message.
   295  	AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"`
   296  	// Flags edits the flags of a message (only SUPPRESS_EMBEDS can currently
   297  	// be set/unset)
   298  	//
   299  	// This field is nullable.
   300  	Flags *discord.MessageFlags `json:"flags,omitempty"`
   301  }
   302  
   303  // EditText edits the contents of a previously sent message. For more
   304  // documentation, refer to EditMessageComplex.
   305  func (c *Client) EditText(
   306  	channelID discord.ChannelID, messageID discord.MessageID, content string) (*discord.Message, error) {
   307  
   308  	return c.EditMessageComplex(channelID, messageID, EditMessageData{
   309  		Content: option.NewNullableString(content),
   310  	})
   311  }
   312  
   313  // EditEmbed edits the embed of a previously sent message. For more
   314  // documentation, refer to EditMessageComplex.
   315  func (c *Client) EditEmbed(
   316  	channelID discord.ChannelID, messageID discord.MessageID, embed discord.Embed) (*discord.Message, error) {
   317  
   318  	return c.EditMessageComplex(channelID, messageID, EditMessageData{
   319  		Embed: &embed,
   320  	})
   321  }
   322  
   323  // EditMessage edits a previously sent message. For more documentation, refer to
   324  // EditMessageComplex.
   325  func (c *Client) EditMessage(
   326  	channelID discord.ChannelID, messageID discord.MessageID, content string,
   327  	embed *discord.Embed, suppressEmbeds bool) (*discord.Message, error) {
   328  
   329  	var data = EditMessageData{
   330  		Content: option.NewNullableString(content),
   331  		Embed:   embed,
   332  	}
   333  	if suppressEmbeds {
   334  		data.Flags = &discord.SuppressEmbeds
   335  	}
   336  
   337  	return c.EditMessageComplex(channelID, messageID, data)
   338  }
   339  
   340  // EditMessageComplex edits a previously sent message. The fields Content,
   341  // Embed, AllowedMentions and Flags can be edited by the original message
   342  // author. Other users can only edit flags and only if they have the
   343  // MANAGE_MESSAGES permission in the corresponding channel. When specifying
   344  // flags, ensure to include all previously set flags/bits in addition to ones
   345  // that you are modifying. Only flags documented in EditMessageData may be
   346  // modified by users (unsupported flag changes are currently ignored without
   347  // error).
   348  //
   349  // Fires a Message Update Gateway event.
   350  func (c *Client) EditMessageComplex(
   351  	channelID discord.ChannelID, messageID discord.MessageID, data EditMessageData) (*discord.Message, error) {
   352  
   353  	if data.AllowedMentions != nil {
   354  		if err := data.AllowedMentions.Verify(); err != nil {
   355  			return nil, errors.Wrap(err, "allowedMentions error")
   356  		}
   357  	}
   358  
   359  	if data.Embed != nil {
   360  		if err := data.Embed.Validate(); err != nil {
   361  			return nil, errors.Wrap(err, "embed error")
   362  		}
   363  	}
   364  
   365  	var msg *discord.Message
   366  	return msg, c.RequestJSON(
   367  		&msg, "PATCH",
   368  		EndpointChannels+channelID.String()+"/messages/"+messageID.String(),
   369  		httputil.WithJSONBody(data),
   370  	)
   371  }
   372  
   373  // CrosspostMessage crossposts a message in a news channel to following channels.
   374  // This endpoint requires the SEND_MESSAGES permission if the current user sent the message,
   375  // or additionally the MANAGE_MESSAGES permission for all other messages.
   376  func (c *Client) CrosspostMessage(channelID discord.ChannelID, messageID discord.MessageID) (*discord.Message, error) {
   377  	var msg *discord.Message
   378  
   379  	return msg, c.RequestJSON(
   380  		&msg,
   381  		"POST",
   382  		EndpointChannels+channelID.String()+"/messages/"+messageID.String()+"/crosspost",
   383  	)
   384  }
   385  
   386  // DeleteMessage delete a message. If operating on a guild channel and trying
   387  // to delete a message that was not sent by the current user, this endpoint
   388  // requires the MANAGE_MESSAGES permission.
   389  func (c *Client) DeleteMessage(channelID discord.ChannelID, messageID discord.MessageID) error {
   390  	return c.FastRequest("DELETE", EndpointChannels+channelID.String()+
   391  		"/messages/"+messageID.String())
   392  }
   393  
   394  // DeleteMessages deletes multiple messages in a single request. This endpoint
   395  // can only be used on guild channels and requires the MANAGE_MESSAGES
   396  // permission. This endpoint only works for bots.
   397  //
   398  // This endpoint will not delete messages older than 2 weeks, and will fail if
   399  // any message provided is older than that or if any duplicate message IDs are
   400  // provided.
   401  //
   402  // Because the underlying endpoint only supports a maximum of 100 message IDs
   403  // per request, DeleteMessages will make a total of messageIDs/100 rounded up
   404  // requests.
   405  //
   406  // Fires a Message Delete Bulk Gateway event.
   407  func (c *Client) DeleteMessages(channelID discord.ChannelID, messageIDs []discord.MessageID) error {
   408  	switch {
   409  	case len(messageIDs) == 0:
   410  		return nil
   411  	case len(messageIDs) == 1:
   412  		return c.DeleteMessage(channelID, messageIDs[0])
   413  	case len(messageIDs) <= maxMessageDeleteLimit: // Fast path
   414  		return c.deleteMessages(channelID, messageIDs)
   415  	}
   416  
   417  	// If the number of messages to be deleted exceeds the amount discord is willing
   418  	// to accept at one time then batches of messages will be deleted
   419  	for start := 0; start < len(messageIDs); start += maxMessageDeleteLimit {
   420  		end := intmath.Min(len(messageIDs), start+maxMessageDeleteLimit)
   421  		err := c.deleteMessages(channelID, messageIDs[start:end])
   422  		if err != nil {
   423  			return err
   424  		}
   425  	}
   426  
   427  	return nil
   428  }
   429  
   430  func (c *Client) deleteMessages(channelID discord.ChannelID, messageIDs []discord.MessageID) error {
   431  	var param struct {
   432  		Messages []discord.MessageID `json:"messages"`
   433  	}
   434  
   435  	param.Messages = messageIDs
   436  
   437  	return c.FastRequest(
   438  		"POST",
   439  		EndpointChannels+channelID.String()+"/messages/bulk-delete",
   440  		httputil.WithJSONBody(param),
   441  	)
   442  }