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