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 }