serenity/model/channel/message.rs
1//! Models relating to Discord channels.
2
3#[cfg(feature = "model")]
4use std::fmt::Display;
5#[cfg(all(feature = "cache", feature = "model"))]
6use std::fmt::Write;
7
8#[cfg(all(feature = "model", feature = "utils"))]
9use crate::builder::{Builder, CreateAllowedMentions, CreateMessage, EditMessage};
10#[cfg(all(feature = "cache", feature = "model"))]
11use crate::cache::{Cache, GuildRef};
12#[cfg(feature = "collector")]
13use crate::collector::{
14 ComponentInteractionCollector,
15 ModalInteractionCollector,
16 ReactionCollector,
17};
18#[cfg(feature = "model")]
19use crate::constants;
20#[cfg(feature = "collector")]
21use crate::gateway::ShardMessenger;
22#[cfg(feature = "model")]
23use crate::http::{CacheHttp, Http};
24use crate::model::prelude::*;
25use crate::model::utils::{discord_colours, StrOrInt};
26#[cfg(all(feature = "model", feature = "cache"))]
27use crate::utils;
28
29/// A representation of a message over a guild's text channel, a group, or a private channel.
30///
31/// [Discord docs](https://discord.com/developers/docs/resources/channel#message-object) with some
32/// [extra fields](https://discord.com/developers/docs/topics/gateway-events#message-create-message-create-extra-fields).
33#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
34#[derive(Clone, Debug, Default, Deserialize, Serialize)]
35#[non_exhaustive]
36pub struct Message {
37 /// The unique Id of the message. Can be used to calculate the creation date of the message.
38 pub id: MessageId,
39 /// The Id of the [`Channel`] that the message was sent to.
40 pub channel_id: ChannelId,
41 /// The user that sent the message.
42 pub author: User,
43 /// The content of the message.
44 pub content: String,
45 /// Initial message creation timestamp, calculated from its Id.
46 pub timestamp: Timestamp,
47 /// The timestamp of the last time the message was updated, if it was.
48 pub edited_timestamp: Option<Timestamp>,
49 /// Indicator of whether the command is to be played back via text-to-speech.
50 ///
51 /// In the client, this is done via the `/tts` slash command.
52 pub tts: bool,
53 /// Indicator of whether the message mentions everyone.
54 pub mention_everyone: bool,
55 /// Array of users mentioned in the message.
56 pub mentions: Vec<User>,
57 /// Array of [`Role`]s' Ids mentioned in the message.
58 pub mention_roles: Vec<RoleId>,
59 /// Channels specifically mentioned in this message.
60 ///
61 /// **Note**: Not all channel mentions in a message will appear in [`Self::mention_channels`].
62 /// Only textual channels that are visible to everyone in a lurkable guild will ever be
63 /// included.
64 ///
65 /// A lurkable guild is one that allows users to read public channels in a server without
66 /// actually joining the server. It also allows users to look at these channels without being
67 /// logged in to Discord.
68 ///
69 /// Only crossposted messages (via Channel Following) currently include
70 /// [`Self::mention_channels`] at all. If no mentions in the message meet these requirements,
71 /// this field will not be sent.
72 ///
73 /// [Refer to Discord's documentation for more information][discord-docs].
74 ///
75 /// [discord-docs]: https://discord.com/developers/docs/resources/channel#message-object
76 #[serde(default = "Vec::new")]
77 pub mention_channels: Vec<ChannelMention>,
78 /// An vector of the files attached to a message.
79 pub attachments: Vec<Attachment>,
80 /// Array of embeds sent with the message.
81 pub embeds: Vec<Embed>,
82 /// Array of reactions performed on the message.
83 #[serde(default)]
84 pub reactions: Vec<MessageReaction>,
85 /// Non-repeating number used for ensuring message order.
86 #[serde(default)]
87 pub nonce: Option<Nonce>,
88 /// Indicator of whether the message is pinned.
89 pub pinned: bool,
90 /// The Id of the webhook that sent this message, if one did.
91 pub webhook_id: Option<WebhookId>,
92 /// Indicator of the type of message this is, i.e. whether it is a regular message or a system
93 /// message.
94 #[serde(rename = "type")]
95 pub kind: MessageType,
96 /// Sent with Rich Presence-related chat embeds.
97 pub activity: Option<MessageActivity>,
98 /// Sent with Rich Presence-related chat embeds.
99 pub application: Option<MessageApplication>,
100 /// If the message is an Interaction or application-owned webhook, this is the id of the
101 /// application.
102 pub application_id: Option<ApplicationId>,
103 /// Reference data sent with crossposted messages.
104 pub message_reference: Option<MessageReference>,
105 /// Bit flags describing extra features of the message.
106 pub flags: Option<MessageFlags>,
107 /// The message that was replied to using this message.
108 pub referenced_message: Option<Box<Message>>, // Boxed to avoid recursion
109 #[cfg_attr(not(ignore_serenity_deprecated), deprecated = "Use interaction_metadata")]
110 pub interaction: Option<Box<MessageInteraction>>,
111 /// Sent if the message is a response to an [`Interaction`].
112 ///
113 /// [`Interaction`]: crate::model::application::Interaction
114 pub interaction_metadata: Option<Box<MessageInteractionMetadata>>,
115 /// The thread that was started from this message, includes thread member object.
116 pub thread: Option<GuildChannel>,
117 /// The components of this message
118 #[serde(default)]
119 pub components: Vec<ActionRow>,
120 /// Array of message sticker item objects.
121 #[serde(default)]
122 pub sticker_items: Vec<StickerItem>,
123 /// A generally increasing integer (there may be gaps or duplicates) that represents the
124 /// approximate position of the message in a thread, it can be used to estimate the relative
125 /// position of the message in a thread in company with total_message_sent on parent thread.
126 pub position: Option<u64>,
127 /// Data of the role subscription purchase or renewal that prompted this
128 /// [`MessageType::RoleSubscriptionPurchase`] message.
129 pub role_subscription_data: Option<RoleSubscriptionData>,
130 // Field omitted: stickers (it's deprecated by Discord)
131 /// The Id of the [`Guild`] that the message was sent in. This value will only be present if
132 /// this message was received over the gateway, therefore **do not use this to check if message
133 /// is in DMs**, it is not a reliable method.
134 // TODO: maybe introduce an `enum MessageLocation { Dm, Guild(GuildId) }` and store
135 // `Option<MessageLocation` here. Instead of None being ambiguous (is it in DMs? Or do we just
136 // not know because HTTP retrieved Messages don't have guild ID?), we'd set
137 // Some(MessageLocation::Dm) in gateway and None in HTTP.
138 pub guild_id: Option<GuildId>,
139 /// A partial amount of data about the user's member data
140 ///
141 /// Only present in [`MessageCreateEvent`].
142 pub member: Option<Box<PartialMember>>,
143 /// A poll that may be attached to a message.
144 ///
145 /// This is often omitted, so is boxed to improve memory usage.
146 ///
147 /// Only present in [`MessageCreateEvent`].
148 pub poll: Option<Box<Poll>>,
149}
150
151#[cfg(feature = "model")]
152impl Message {
153 /// Crossposts this message.
154 ///
155 /// Requires either to be the message author or to have manage [Manage Messages] permissions on
156 /// this channel.
157 ///
158 /// **Note**: Only available on news channels.
159 ///
160 /// # Errors
161 ///
162 /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] if the current user
163 /// does not have the required permissions.
164 ///
165 /// Returns a [`ModelError::MessageAlreadyCrossposted`] if the message has already been
166 /// crossposted.
167 ///
168 /// Returns a [`ModelError::CannotCrosspostMessage`] if the message cannot be crossposted.
169 ///
170 /// [Manage Messages]: Permissions::MANAGE_MESSAGES
171 pub async fn crosspost(&self, cache_http: impl CacheHttp) -> Result<Message> {
172 #[cfg(feature = "cache")]
173 {
174 if let Some(cache) = cache_http.cache() {
175 if self.author.id != cache.current_user().id && self.guild_id.is_some() {
176 utils::user_has_perms_cache(
177 cache,
178 self.channel_id,
179 Permissions::MANAGE_MESSAGES,
180 )?;
181 }
182 }
183 }
184
185 if let Some(flags) = self.flags {
186 if flags.contains(MessageFlags::CROSSPOSTED) {
187 return Err(Error::Model(ModelError::MessageAlreadyCrossposted));
188 } else if flags.contains(MessageFlags::IS_CROSSPOST)
189 || self.kind != MessageType::Regular
190 {
191 return Err(Error::Model(ModelError::CannotCrosspostMessage));
192 }
193 }
194
195 self.channel_id.crosspost(cache_http.http(), self.id).await
196 }
197
198 /// First attempts to find a [`Channel`] by its Id in the cache, upon failure requests it via
199 /// the REST API.
200 ///
201 /// **Note**: If the `cache`-feature is enabled permissions will be checked and upon owning the
202 /// required permissions the HTTP-request will be issued.
203 ///
204 /// # Errors
205 ///
206 /// Can return an error if the HTTP request fails.
207 #[inline]
208 pub async fn channel(&self, cache_http: impl CacheHttp) -> Result<Channel> {
209 self.channel_id.to_channel(cache_http).await
210 }
211
212 /// A util function for determining whether this message was sent by someone else, or the bot.
213 #[cfg(feature = "cache")]
214 #[deprecated = "Check Message::author is equal to Cache::current_user"]
215 pub fn is_own(&self, cache: impl AsRef<Cache>) -> bool {
216 self.author.id == cache.as_ref().current_user().id
217 }
218
219 /// Calculates the permissions of the message author in the current channel.
220 ///
221 /// This handles the [`Permissions::SEND_MESSAGES_IN_THREADS`] permission for threads, setting
222 /// [`Permissions::SEND_MESSAGES`] accordingly if this message was sent in a thread.
223 ///
224 /// This may return `None` if:
225 /// - The [`Cache`] does not have the current [`Guild`]
226 /// - The [`Guild`] does not have the current channel cached (should never happen).
227 /// - This message is not from [`MessageCreateEvent`] and the author's [`Member`] cannot be
228 /// found in [`Guild#structfield.members`].
229 #[cfg(feature = "cache")]
230 pub fn author_permissions(&self, cache: impl AsRef<Cache>) -> Option<Permissions> {
231 let Some(guild_id) = self.guild_id else {
232 return Some(Permissions::dm_permissions());
233 };
234
235 let guild = cache.as_ref().guild(guild_id)?;
236 let (channel, is_thread) = if let Some(channel) = guild.channels.get(&self.channel_id) {
237 (channel, false)
238 } else if let Some(thread) = guild.threads.iter().find(|th| th.id == self.channel_id) {
239 (thread, true)
240 } else {
241 return None;
242 };
243
244 let mut permissions = if let Some(member) = &self.member {
245 guild.partial_member_permissions_in(channel, self.author.id, member)
246 } else {
247 guild.user_permissions_in(channel, guild.members.get(&self.author.id)?)
248 };
249
250 if is_thread {
251 permissions.set(Permissions::SEND_MESSAGES, permissions.send_messages_in_threads());
252 }
253
254 Some(permissions)
255 }
256
257 /// Deletes the message.
258 ///
259 /// **Note**: The logged in user must either be the author of the message or have the [Manage
260 /// Messages] permission.
261 ///
262 /// # Errors
263 ///
264 /// If the `cache` feature is enabled, then returns a [`ModelError::InvalidPermissions`] if the
265 /// current user does not have the required permissions.
266 ///
267 /// [Manage Messages]: Permissions::MANAGE_MESSAGES
268 pub async fn delete(&self, cache_http: impl CacheHttp) -> Result<()> {
269 #[cfg(feature = "cache")]
270 {
271 if let Some(cache) = cache_http.cache() {
272 if self.author.id != cache.current_user().id {
273 utils::user_has_perms_cache(
274 cache,
275 self.channel_id,
276 Permissions::MANAGE_MESSAGES,
277 )?;
278 }
279 }
280 }
281
282 self.channel_id.delete_message(cache_http.http(), self.id).await
283 }
284
285 /// Deletes all of the [`Reaction`]s associated with the message.
286 ///
287 /// **Note**: Requires the [Manage Messages] permission.
288 ///
289 /// # Errors
290 ///
291 /// If the `cache` feature is enabled, then returns a [`ModelError::InvalidPermissions`] if the
292 /// current user does not have the required permissions.
293 ///
294 /// [Manage Messages]: Permissions::MANAGE_MESSAGES
295 pub async fn delete_reactions(&self, cache_http: impl CacheHttp) -> Result<()> {
296 #[cfg(feature = "cache")]
297 {
298 if let Some(cache) = cache_http.cache() {
299 utils::user_has_perms_cache(cache, self.channel_id, Permissions::MANAGE_MESSAGES)?;
300 }
301 }
302
303 self.channel_id.delete_reactions(cache_http.http(), self.id).await
304 }
305
306 /// Deletes the given [`Reaction`] from the message.
307 ///
308 /// **Note**: Requires the [Manage Messages] permission, _if_ the current user did not perform
309 /// the reaction.
310 ///
311 /// # Errors
312 ///
313 /// Returns [`Error::Http`] if the current user did not perform the reaction, or lacks
314 /// permission.
315 ///
316 /// [Manage Messages]: Permissions::MANAGE_MESSAGES
317 #[inline]
318 pub async fn delete_reaction(
319 &self,
320 http: impl AsRef<Http>,
321 user_id: Option<UserId>,
322 reaction_type: impl Into<ReactionType>,
323 ) -> Result<()> {
324 self.channel_id.delete_reaction(http, self.id, user_id, reaction_type).await
325 }
326
327 /// Deletes all of the [`Reaction`]s of a given emoji associated with the message.
328 ///
329 /// **Note**: Requires the [Manage Messages] permission.
330 ///
331 /// # Errors
332 ///
333 /// If the `cache` feature is enabled, then returns a [`ModelError::InvalidPermissions`] if the
334 /// current user does not have the required permissions.
335 ///
336 /// [Manage Messages]: Permissions::MANAGE_MESSAGES
337 pub async fn delete_reaction_emoji(
338 &self,
339 cache_http: impl CacheHttp,
340 reaction_type: impl Into<ReactionType>,
341 ) -> Result<()> {
342 #[cfg(feature = "cache")]
343 {
344 if let Some(cache) = cache_http.cache() {
345 utils::user_has_perms_cache(cache, self.channel_id, Permissions::MANAGE_MESSAGES)?;
346 }
347 }
348
349 cache_http
350 .http()
351 .as_ref()
352 .delete_message_reaction_emoji(self.channel_id, self.id, &reaction_type.into())
353 .await
354 }
355
356 /// Edits this message, replacing the original content with new content.
357 ///
358 /// Message editing preserves all unchanged message data, with some exceptions for embeds and
359 /// attachments.
360 ///
361 /// **Note**: In most cases requires that the current user be the author of the message.
362 ///
363 /// Refer to the documentation for [`EditMessage`] for information regarding content
364 /// restrictions and requirements.
365 ///
366 /// # Examples
367 ///
368 /// Edit a message with new content:
369 ///
370 /// ```rust,no_run
371 /// # use serenity::builder::EditMessage;
372 /// # use serenity::model::channel::Message;
373 /// # use serenity::model::id::ChannelId;
374 /// # use serenity::http::Http;
375 /// #
376 /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
377 /// # let http: Http = unimplemented!();
378 /// # let mut message: Message = unimplemented!();
379 /// // assuming a `message` has already been bound
380 /// let builder = EditMessage::new().content("new content");
381 /// message.edit(&http, builder).await?;
382 /// # Ok(())
383 /// # }
384 /// ```
385 ///
386 /// # Errors
387 ///
388 /// If the `cache` is enabled, returns a [`ModelError::InvalidUser`] if the current user is not
389 /// the author. Otherwise returns [`Error::Http`] if the user lacks permission, as well as if
390 /// invalid data is given.
391 ///
392 /// Returns a [`ModelError::MessageTooLong`] if the message contents are too long.
393 ///
394 /// [Manage Messages]: Permissions::MANAGE_MESSAGES
395 pub async fn edit(&mut self, cache_http: impl CacheHttp, builder: EditMessage) -> Result<()> {
396 if let Some(flags) = self.flags {
397 if flags.contains(MessageFlags::IS_VOICE_MESSAGE) {
398 return Err(Error::Model(ModelError::CannotEditVoiceMessage));
399 }
400 }
401
402 *self =
403 builder.execute(cache_http, (self.channel_id, self.id, Some(self.author.id))).await?;
404 Ok(())
405 }
406
407 /// Returns message content, but with user and role mentions replaced with
408 /// names and everyone/here mentions cancelled.
409 #[cfg(feature = "cache")]
410 pub fn content_safe(&self, cache: impl AsRef<Cache>) -> String {
411 let mut result = self.content.clone();
412
413 // First replace all user mentions.
414 for u in &self.mentions {
415 let mut at_distinct = String::with_capacity(38);
416 at_distinct.push('@');
417 at_distinct.push_str(&u.name);
418 if let Some(discriminator) = u.discriminator {
419 at_distinct.push('#');
420 write!(at_distinct, "{:04}", discriminator.get()).unwrap();
421 }
422
423 let mut m = u.mention().to_string();
424 // Check whether we're replacing a nickname mention or a normal mention.
425 // `UserId::mention` returns a normal mention. If it isn't present in the message, it's
426 // a nickname mention.
427 if !result.contains(&m) {
428 m.insert(2, '!');
429 }
430
431 result = result.replace(&m, &at_distinct);
432 }
433
434 // Then replace all role mentions.
435 if let Some(guild_id) = self.guild_id {
436 for id in &self.mention_roles {
437 let mention = id.mention().to_string();
438
439 if let Some(guild) = cache.as_ref().guild(guild_id) {
440 if let Some(role) = guild.roles.get(id) {
441 result = result.replace(&mention, &format!("@{}", role.name));
442 continue;
443 }
444 }
445
446 result = result.replace(&mention, "@deleted-role");
447 }
448 }
449
450 // And finally replace everyone and here mentions.
451 result.replace("@everyone", "@\u{200B}everyone").replace("@here", "@\u{200B}here")
452 }
453
454 /// Gets the list of [`User`]s who have reacted to a [`Message`] with a certain [`Emoji`].
455 ///
456 /// The default `limit` is `50` - specify otherwise to receive a different maximum number of
457 /// users. The maximum that may be retrieve at a time is `100`, if a greater number is provided
458 /// then it is automatically reduced.
459 ///
460 /// The optional `after` attribute is to retrieve the users after a certain user. This is
461 /// useful for pagination.
462 ///
463 /// **Note**: Requires the [Read Message History] permission.
464 ///
465 /// **Note**: If the passed reaction_type is a custom guild emoji, it must contain the name.
466 /// So, [`Emoji`] or [`EmojiIdentifier`] will always work, [`ReactionType`] only if
467 /// [`ReactionType::Custom::name`] is Some, and **[`EmojiId`] will never work**.
468 ///
469 /// # Errors
470 ///
471 /// Returns [`Error::Http`] if the current user lacks permission.
472 ///
473 /// [Read Message History]: Permissions::READ_MESSAGE_HISTORY
474 #[inline]
475 pub async fn reaction_users(
476 &self,
477 http: impl AsRef<Http>,
478 reaction_type: impl Into<ReactionType>,
479 limit: Option<u8>,
480 after: impl Into<Option<UserId>>,
481 ) -> Result<Vec<User>> {
482 self.channel_id.reaction_users(http, self.id, reaction_type, limit, after).await
483 }
484
485 /// Returns the associated [`Guild`] for the message if one is in the cache.
486 ///
487 /// Returns [`None`] if the guild's Id could not be found via [`Self::guild_id`] or if the
488 /// Guild itself is not cached.
489 ///
490 /// Requires the `cache` feature be enabled.
491 #[cfg(feature = "cache")]
492 pub fn guild<'a>(&self, cache: &'a Cache) -> Option<GuildRef<'a>> {
493 cache.guild(self.guild_id?)
494 }
495
496 /// True if message was sent using direct messages.
497 ///
498 /// **Only use this for messages from the gateway (event handler)!** Not for returned Message
499 /// objects from HTTP requests, like [`ChannelId::send_message`], because [`Self::guild_id`] is
500 /// never set for those, which this method relies on.
501 #[inline]
502 #[must_use]
503 #[deprecated = "Check if guild_id is None if the message is received from the gateway."]
504 pub fn is_private(&self) -> bool {
505 self.guild_id.is_none()
506 }
507
508 /// Retrieves a clone of the author's Member instance, if this message was sent in a guild.
509 ///
510 /// If the instance cannot be found in the cache, or the `cache` feature is disabled, a HTTP
511 /// request is performed to retrieve it from Discord's API.
512 ///
513 /// # Errors
514 ///
515 /// [`ModelError::ItemMissing`] is returned if [`Self::guild_id`] is [`None`].
516 pub async fn member(&self, cache_http: impl CacheHttp) -> Result<Member> {
517 match self.guild_id {
518 Some(guild_id) => guild_id.member(cache_http, self.author.id).await,
519 None => Err(Error::Model(ModelError::ItemMissing)),
520 }
521 }
522
523 /// Checks the length of a message to ensure that it is within Discord's maximum length limit.
524 ///
525 /// Returns [`None`] if the message is within the limit, otherwise returns [`Some`] with an
526 /// inner value of how many unicode code points the message is over.
527 #[must_use]
528 pub fn overflow_length(content: &str) -> Option<usize> {
529 crate::builder::check_overflow(content.chars().count(), constants::MESSAGE_CODE_LIMIT).err()
530 }
531
532 /// Pins this message to its channel.
533 ///
534 /// **Note**: Requires the [Manage Messages] permission.
535 ///
536 /// # Errors
537 ///
538 /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] if the current user
539 /// does not have the required permissions.
540 ///
541 /// [Manage Messages]: Permissions::MANAGE_MESSAGES
542 pub async fn pin(&self, cache_http: impl CacheHttp) -> Result<()> {
543 #[cfg(feature = "cache")]
544 {
545 if let Some(cache) = cache_http.cache() {
546 if self.guild_id.is_some() {
547 utils::user_has_perms_cache(
548 cache,
549 self.channel_id,
550 Permissions::MANAGE_MESSAGES,
551 )?;
552 }
553 }
554 }
555
556 self.channel_id.pin(cache_http.http(), self.id).await
557 }
558
559 /// React to the message with a custom [`Emoji`] or unicode character.
560 ///
561 /// **Note**: Requires the [Add Reactions] permission.
562 ///
563 /// # Errors
564 ///
565 /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] if the current user
566 /// does not have the required [permissions].
567 ///
568 /// [Add Reactions]: Permissions::ADD_REACTIONS
569 /// [permissions]: crate::model::permissions
570 #[inline]
571 pub async fn react(
572 &self,
573 cache_http: impl CacheHttp,
574 reaction_type: impl Into<ReactionType>,
575 ) -> Result<Reaction> {
576 self.react_(cache_http, reaction_type.into(), false).await
577 }
578
579 /// React to the message with a custom [`Emoji`] or unicode character.
580 ///
581 /// **Note**: Requires [Add Reactions] and [Use External Emojis] permissions.
582 ///
583 /// # Errors
584 ///
585 /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] if the current user
586 /// does not have the required [permissions].
587 ///
588 /// [Add Reactions]: Permissions::ADD_REACTIONS
589 /// [Use External Emojis]: Permissions::USE_EXTERNAL_EMOJIS
590 /// [permissions]: crate::model::permissions
591 #[inline]
592 pub async fn super_react(
593 &self,
594 cache_http: impl CacheHttp,
595 reaction_type: impl Into<ReactionType>,
596 ) -> Result<Reaction> {
597 self.react_(cache_http, reaction_type.into(), true).await
598 }
599
600 async fn react_(
601 &self,
602 cache_http: impl CacheHttp,
603 reaction_type: ReactionType,
604 burst: bool,
605 ) -> Result<Reaction> {
606 #[cfg_attr(not(feature = "cache"), allow(unused_mut))]
607 let mut user_id = None;
608
609 #[cfg(feature = "cache")]
610 {
611 if let Some(cache) = cache_http.cache() {
612 if self.guild_id.is_some() {
613 utils::user_has_perms_cache(
614 cache,
615 self.channel_id,
616 Permissions::ADD_REACTIONS,
617 )?;
618
619 if burst {
620 utils::user_has_perms_cache(
621 cache,
622 self.channel_id,
623 Permissions::USE_EXTERNAL_EMOJIS,
624 )?;
625 }
626 }
627
628 user_id = Some(cache.current_user().id);
629 }
630 }
631
632 let reaction_types = if burst {
633 cache_http
634 .http()
635 .create_super_reaction(self.channel_id, self.id, &reaction_type)
636 .await?;
637 ReactionTypes::Burst
638 } else {
639 cache_http.http().create_reaction(self.channel_id, self.id, &reaction_type).await?;
640 ReactionTypes::Normal
641 };
642
643 Ok(Reaction {
644 channel_id: self.channel_id,
645 emoji: reaction_type,
646 message_id: self.id,
647 user_id,
648 guild_id: self.guild_id,
649 member: self.member.as_deref().map(|member| member.clone().into()),
650 message_author_id: None,
651 burst,
652 burst_colours: None,
653 reaction_type: reaction_types,
654 })
655 }
656
657 /// Uses Discord's inline reply to a user without pinging them.
658 ///
659 /// User mentions are generally around 20 or 21 characters long.
660 ///
661 /// **Note**: Requires the [Send Messages] permission.
662 ///
663 /// **Note**: Message contents must be under 2000 unicode code points.
664 ///
665 /// # Errors
666 ///
667 /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] if the current user
668 /// does not have the required permissions.
669 ///
670 /// Returns a [`ModelError::MessageTooLong`] if the content of the message is over the above
671 /// limit, containing the number of unicode code points over the limit.
672 ///
673 /// [Send Messages]: Permissions::SEND_MESSAGES
674 #[inline]
675 pub async fn reply(
676 &self,
677 cache_http: impl CacheHttp,
678 content: impl Into<String>,
679 ) -> Result<Message> {
680 self.reply_(cache_http, content, Some(false)).await
681 }
682
683 /// Uses Discord's inline reply to a user with a ping.
684 ///
685 /// **Note**: Requires the [Send Messages] permission.
686 ///
687 /// **Note**: Message contents must be under 2000 unicode code points.
688 ///
689 /// # Errors
690 ///
691 /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] if the current user
692 /// does not have the required permissions.
693 ///
694 /// Returns a [`ModelError::MessageTooLong`] if the content of the message is over the above
695 /// limit, containing the number of unicode code points over the limit.
696 ///
697 /// [Send Messages]: Permissions::SEND_MESSAGES
698 #[inline]
699 pub async fn reply_ping(
700 &self,
701 cache_http: impl CacheHttp,
702 content: impl Into<String>,
703 ) -> Result<Message> {
704 self.reply_(cache_http, content, Some(true)).await
705 }
706
707 /// Replies to the user, mentioning them prior to the content in the form of: `@<USER_ID>
708 /// YOUR_CONTENT`.
709 ///
710 /// User mentions are generally around 20 or 21 characters long.
711 ///
712 /// **Note**: Requires the [Send Messages] permission.
713 ///
714 /// **Note**: Message contents must be under 2000 unicode code points.
715 ///
716 /// # Errors
717 ///
718 /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] if the current user
719 /// does not have the required permissions.
720 ///
721 /// Returns a [`ModelError::MessageTooLong`] if the content of the message is over the above
722 /// limit, containing the number of unicode code points over the limit.
723 ///
724 /// [Send Messages]: Permissions::SEND_MESSAGES
725 #[inline]
726 pub async fn reply_mention(
727 &self,
728 cache_http: impl CacheHttp,
729 content: impl Display,
730 ) -> Result<Message> {
731 self.reply_(cache_http, format!("{} {content}", self.author.mention()), None).await
732 }
733
734 /// `inlined` decides whether this reply is inlined and whether it pings.
735 async fn reply_(
736 &self,
737 cache_http: impl CacheHttp,
738 content: impl Into<String>,
739 inlined: Option<bool>,
740 ) -> Result<Message> {
741 #[cfg(feature = "cache")]
742 {
743 if let Some(cache) = cache_http.cache() {
744 if self.guild_id.is_some() {
745 utils::user_has_perms_cache(
746 cache,
747 self.channel_id,
748 Permissions::SEND_MESSAGES,
749 )?;
750 }
751 }
752 }
753
754 let mut builder = CreateMessage::new().content(content);
755 if let Some(ping_user) = inlined {
756 let allowed_mentions = CreateAllowedMentions::new()
757 .replied_user(ping_user)
758 // By providing allowed_mentions, Discord disabled _all_ pings by default so we
759 // need to re-enable them
760 .everyone(true)
761 .all_users(true)
762 .all_roles(true);
763 builder = builder.reference_message(self).allowed_mentions(allowed_mentions);
764 }
765 self.channel_id.send_message(cache_http, builder).await
766 }
767
768 /// Checks whether the message mentions passed [`UserId`].
769 #[inline]
770 pub fn mentions_user_id(&self, id: impl Into<UserId>) -> bool {
771 let id = id.into();
772 self.mentions.iter().any(|mentioned_user| mentioned_user.id == id)
773 }
774
775 /// Checks whether the message mentions passed [`User`].
776 #[inline]
777 #[must_use]
778 pub fn mentions_user(&self, user: &User) -> bool {
779 self.mentions_user_id(user.id)
780 }
781
782 /// Checks whether the message mentions the current user.
783 ///
784 /// # Errors
785 ///
786 /// May return [`Error::Http`] if the `cache` feature is not enabled, or if the cache is
787 /// otherwise unavailable.
788 pub async fn mentions_me(&self, cache_http: impl CacheHttp) -> Result<bool> {
789 #[cfg(feature = "cache")]
790 {
791 if let Some(cache) = cache_http.cache() {
792 return Ok(self.mentions_user_id(cache.current_user().id));
793 }
794 }
795
796 let current_user = cache_http.http().get_current_user().await?;
797 Ok(self.mentions_user_id(current_user.id))
798 }
799
800 /// Unpins the message from its channel.
801 ///
802 /// **Note**: Requires the [Manage Messages] permission.
803 ///
804 /// # Errors
805 ///
806 /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] if the current user
807 /// does not have the required permissions.
808 ///
809 /// [Manage Messages]: Permissions::MANAGE_MESSAGES
810 pub async fn unpin(&self, cache_http: impl CacheHttp) -> Result<()> {
811 #[cfg(feature = "cache")]
812 {
813 if let Some(cache) = cache_http.cache() {
814 if self.guild_id.is_some() {
815 utils::user_has_perms_cache(
816 cache,
817 self.channel_id,
818 Permissions::MANAGE_MESSAGES,
819 )?;
820 }
821 }
822 }
823
824 cache_http.http().unpin_message(self.channel_id, self.id, None).await
825 }
826
827 /// Ends the [`Poll`] on this message, if there is one.
828 ///
829 /// # Errors
830 ///
831 /// See [`ChannelId::end_poll`] for more information.
832 pub async fn end_poll(&self, http: impl AsRef<Http>) -> Result<Self> {
833 self.channel_id.end_poll(http, self.id).await
834 }
835
836 /// Tries to return author's nickname in the current channel's guild.
837 ///
838 /// Refer to [`User::nick_in()`] inside and [`None`] outside of a guild.
839 #[inline]
840 pub async fn author_nick(&self, cache_http: impl CacheHttp) -> Option<String> {
841 self.author.nick_in(cache_http, self.guild_id?).await
842 }
843
844 /// Returns a link referencing this message. When clicked, users will jump to the message. The
845 /// link will be valid for messages in either private channels or guilds.
846 #[inline]
847 #[must_use]
848 pub fn link(&self) -> String {
849 self.id.link(self.channel_id, self.guild_id)
850 }
851
852 /// Same as [`Self::link`] but tries to find the [`GuildId`] if Discord does not provide it.
853 ///
854 /// [`guild_id`]: Self::guild_id
855 #[inline]
856 #[allow(deprecated)]
857 #[deprecated = "Use Self::link if Message was recieved via an event, otherwise use MessageId::link to provide the guild_id yourself."]
858 pub async fn link_ensured(&self, cache_http: impl CacheHttp) -> String {
859 self.id.link_ensured(cache_http, self.channel_id, self.guild_id).await
860 }
861
862 /// Returns a builder which can be awaited to obtain a reaction or stream of reactions on this
863 /// message.
864 #[cfg(feature = "collector")]
865 pub fn await_reaction(&self, shard_messenger: impl AsRef<ShardMessenger>) -> ReactionCollector {
866 ReactionCollector::new(shard_messenger).message_id(self.id)
867 }
868
869 /// Same as [`Self::await_reaction`].
870 #[cfg(feature = "collector")]
871 pub fn await_reactions(
872 &self,
873 shard_messenger: impl AsRef<ShardMessenger>,
874 ) -> ReactionCollector {
875 self.await_reaction(shard_messenger)
876 }
877
878 /// Returns a builder which can be awaited to obtain a single component interactions or a
879 /// stream of component interactions on this message.
880 #[cfg(feature = "collector")]
881 pub fn await_component_interaction(
882 &self,
883 shard_messenger: impl AsRef<ShardMessenger>,
884 ) -> ComponentInteractionCollector {
885 ComponentInteractionCollector::new(shard_messenger).message_id(self.id)
886 }
887
888 /// Same as [`Self::await_component_interaction`].
889 #[cfg(feature = "collector")]
890 pub fn await_component_interactions(
891 &self,
892 shard_messenger: impl AsRef<ShardMessenger>,
893 ) -> ComponentInteractionCollector {
894 self.await_component_interaction(shard_messenger)
895 }
896
897 /// Returns a builder which can be awaited to obtain a model submit interaction or stream of
898 /// modal submit interactions on this message.
899 #[cfg(feature = "collector")]
900 pub fn await_modal_interaction(
901 &self,
902 shard_messenger: impl AsRef<ShardMessenger>,
903 ) -> ModalInteractionCollector {
904 ModalInteractionCollector::new(shard_messenger).message_id(self.id)
905 }
906
907 /// Same as [`Self::await_modal_interaction`].
908 #[cfg(feature = "collector")]
909 pub fn await_modal_interactions(
910 &self,
911 shard_messenger: impl AsRef<ShardMessenger>,
912 ) -> ModalInteractionCollector {
913 self.await_modal_interaction(shard_messenger)
914 }
915
916 /// Retrieves the message channel's category ID if the channel has one.
917 pub async fn category_id(&self, cache_http: impl CacheHttp) -> Option<ChannelId> {
918 #[cfg(feature = "cache")]
919 if let Some(cache) = cache_http.cache() {
920 if let Some(guild) = cache.guild(self.guild_id?) {
921 let channel = guild.channels.get(&self.channel_id)?;
922 return if channel.thread_metadata.is_some() {
923 let thread_parent = guild.channels.get(&channel.parent_id?)?;
924 thread_parent.parent_id
925 } else {
926 channel.parent_id
927 };
928 }
929 }
930
931 let channel = self.channel_id.to_channel(&cache_http).await.ok()?.guild()?;
932 if channel.thread_metadata.is_some() {
933 let thread_parent = channel.parent_id?.to_channel(cache_http).await.ok()?.guild()?;
934 thread_parent.parent_id
935 } else {
936 channel.parent_id
937 }
938 }
939}
940
941impl AsRef<MessageId> for Message {
942 fn as_ref(&self) -> &MessageId {
943 &self.id
944 }
945}
946
947impl From<Message> for MessageId {
948 /// Gets the Id of a [`Message`].
949 fn from(message: Message) -> MessageId {
950 message.id
951 }
952}
953
954impl From<&Message> for MessageId {
955 /// Gets the Id of a [`Message`].
956 fn from(message: &Message) -> MessageId {
957 message.id
958 }
959}
960
961/// A representation of a reaction to a message.
962///
963/// Multiple of the same [reaction type] are sent into one [`MessageReaction`], with an associated
964/// [`Self::count`].
965///
966/// [Discord docs](https://discord.com/developers/docs/resources/channel#reaction-object).
967///
968/// [reaction type]: ReactionType
969#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
970#[derive(Clone, Debug, Deserialize, Serialize)]
971#[non_exhaustive]
972pub struct MessageReaction {
973 /// The amount of the type of reaction that have been sent for the associated message
974 /// including super reactions.
975 pub count: u64,
976 /// A breakdown of what reactions were from regular reactions and super reactions.
977 pub count_details: CountDetails,
978 /// Indicator of whether the current user has sent this type of reaction.
979 pub me: bool,
980 /// Indicator of whether the current user has sent the type of super-reaction.
981 pub me_burst: bool,
982 /// The type of reaction.
983 #[serde(rename = "emoji")]
984 pub reaction_type: ReactionType,
985 // The colours used for super reactions.
986 #[serde(rename = "burst_colors", deserialize_with = "discord_colours")]
987 pub burst_colours: Vec<Colour>,
988}
989
990/// A representation of reaction count details.
991///
992/// [Discord docs](https://discord.com/developers/docs/resources/channel#reaction-count-details-object).
993#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
994#[derive(Clone, Debug, Deserialize, Serialize)]
995#[non_exhaustive]
996pub struct CountDetails {
997 pub burst: u64,
998 pub normal: u64,
999}
1000
1001enum_number! {
1002 /// Differentiates between regular and different types of system messages.
1003 ///
1004 /// [Discord docs](https://discord.com/developers/docs/resources/channel#message-object-message-types).
1005 #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
1006 #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1007 #[serde(from = "u8", into = "u8")]
1008 #[non_exhaustive]
1009 pub enum MessageType {
1010 /// A regular message.
1011 #[default]
1012 Regular = 0,
1013 /// An indicator that a recipient was added by the author.
1014 GroupRecipientAddition = 1,
1015 /// An indicator that a recipient was removed by the author.
1016 GroupRecipientRemoval = 2,
1017 /// An indicator that a call was started by the author.
1018 GroupCallCreation = 3,
1019 /// An indicator that the group name was modified by the author.
1020 GroupNameUpdate = 4,
1021 /// An indicator that the group icon was modified by the author.
1022 GroupIconUpdate = 5,
1023 /// An indicator that a message was pinned by the author.
1024 PinsAdd = 6,
1025 /// An indicator that a member joined the guild.
1026 MemberJoin = 7,
1027 /// An indicator that someone has boosted the guild.
1028 NitroBoost = 8,
1029 /// An indicator that the guild has reached nitro tier 1
1030 NitroTier1 = 9,
1031 /// An indicator that the guild has reached nitro tier 2
1032 NitroTier2 = 10,
1033 /// An indicator that the guild has reached nitro tier 3
1034 NitroTier3 = 11,
1035 /// An indicator that the channel is now following a news channel
1036 ChannelFollowAdd = 12,
1037 /// An indicator that the guild is disqualified for Discovery Feature
1038 GuildDiscoveryDisqualified = 14,
1039 /// An indicator that the guild is requalified for Discovery Feature
1040 GuildDiscoveryRequalified = 15,
1041 /// The first warning before guild discovery removal.
1042 GuildDiscoveryGracePeriodInitialWarning = 16,
1043 /// The last warning before guild discovery removal.
1044 GuildDiscoveryGracePeriodFinalWarning = 17,
1045 /// Message sent to inform users that a thread was created.
1046 ThreadCreated = 18,
1047 /// A message reply.
1048 InlineReply = 19,
1049 /// A slash command.
1050 ChatInputCommand = 20,
1051 /// A thread start message.
1052 ThreadStarterMessage = 21,
1053 /// Server setup tips.
1054 GuildInviteReminder = 22,
1055 /// A context menu command.
1056 ContextMenuCommand = 23,
1057 /// A message from an auto moderation action.
1058 AutoModAction = 24,
1059 RoleSubscriptionPurchase = 25,
1060 InteractionPremiumUpsell = 26,
1061 StageStart = 27,
1062 StageEnd = 28,
1063 StageSpeaker = 29,
1064 StageTopic = 31,
1065 GuildApplicationPremiumSubscription = 32,
1066 GuildIncidentAlertModeEnabled = 36,
1067 GuildIncidentAlertModeDisabled = 37,
1068 GuildIncidentReportRaid = 38,
1069 GuildIncidentReportFalseAlarm = 39,
1070 _ => Unknown(u8),
1071 }
1072}
1073
1074enum_number! {
1075 /// [Discord docs](https://discord.com/developers/docs/resources/channel#message-object-message-activity-types).
1076 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
1077 #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1078 #[serde(from = "u8", into = "u8")]
1079 #[non_exhaustive]
1080 pub enum MessageActivityKind {
1081 Join = 1,
1082 Spectate = 2,
1083 Listen = 3,
1084 JoinRequest = 5,
1085 _ => Unknown(u8),
1086 }
1087}
1088
1089/// Rich Presence application information.
1090///
1091/// [Discord docs](https://discord.com/developers/docs/resources/application#application-object),
1092/// [subset undocumented](https://discord.com/developers/docs/resources/channel#message-object-message-structure).
1093#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1094#[derive(Clone, Debug, Deserialize, Serialize)]
1095#[non_exhaustive]
1096pub struct MessageApplication {
1097 /// ID of the application.
1098 pub id: ApplicationId,
1099 /// ID of the embed's image asset.
1100 pub cover_image: Option<ImageHash>,
1101 /// Application's description.
1102 pub description: String,
1103 /// ID of the application's icon.
1104 pub icon: Option<ImageHash>,
1105 /// Name of the application.
1106 pub name: String,
1107}
1108
1109/// Rich Presence activity information.
1110///
1111/// [Discord docs](https://discord.com/developers/docs/resources/channel#message-object-message-activity-structure).
1112#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1113#[derive(Clone, Debug, Deserialize, Serialize)]
1114#[non_exhaustive]
1115pub struct MessageActivity {
1116 /// Kind of message activity.
1117 #[serde(rename = "type")]
1118 pub kind: MessageActivityKind,
1119 /// `party_id` from a Rich Presence event.
1120 pub party_id: Option<String>,
1121}
1122
1123enum_number! {
1124 /// Message Reference Type information
1125 ///
1126 /// [Discord docs](https://discord.com/developers/docs/resources/message#message-reference-types)
1127 #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
1128 #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1129 #[serde(from = "u8", into = "u8")]
1130 #[non_exhaustive]
1131 pub enum MessageReferenceKind {
1132 #[default]
1133 Default = 0,
1134 Forward = 1,
1135 _ => Unknown(u8),
1136 }
1137}
1138
1139/// Reference data sent with crossposted messages.
1140///
1141/// [Discord docs](https://discord.com/developers/docs/resources/channel#message-reference-object-message-reference-structure).
1142#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1143#[derive(Clone, Debug, Deserialize, Serialize)]
1144#[non_exhaustive]
1145pub struct MessageReference {
1146 /// The Type of Message Reference
1147 #[serde(rename = "type", default = "MessageReferenceKind::default")]
1148 pub kind: MessageReferenceKind,
1149 /// ID of the originating message.
1150 pub message_id: Option<MessageId>,
1151 /// ID of the originating message's channel.
1152 pub channel_id: ChannelId,
1153 /// ID of the originating message's guild.
1154 pub guild_id: Option<GuildId>,
1155 /// When sending, whether to error if the referenced message doesn't exist instead of sending
1156 /// as a normal (non-reply) message, default true.
1157 pub fail_if_not_exists: Option<bool>,
1158}
1159
1160impl MessageReference {
1161 #[must_use]
1162 pub fn new(kind: MessageReferenceKind, channel_id: ChannelId) -> Self {
1163 Self {
1164 kind,
1165 channel_id,
1166 message_id: None,
1167 guild_id: None,
1168 fail_if_not_exists: None,
1169 }
1170 }
1171
1172 #[must_use]
1173 pub fn message_id(mut self, message_id: MessageId) -> Self {
1174 self.message_id = Some(message_id);
1175 self
1176 }
1177
1178 #[must_use]
1179 pub fn guild_id(mut self, guild_id: GuildId) -> Self {
1180 self.guild_id = Some(guild_id);
1181 self
1182 }
1183
1184 #[must_use]
1185 pub fn fail_if_not_exists(mut self, fail_if_not_exists: bool) -> Self {
1186 self.fail_if_not_exists = Some(fail_if_not_exists);
1187 self
1188 }
1189}
1190
1191impl From<&Message> for MessageReference {
1192 fn from(m: &Message) -> Self {
1193 Self {
1194 kind: MessageReferenceKind::default(),
1195 message_id: Some(m.id),
1196 channel_id: m.channel_id,
1197 guild_id: m.guild_id,
1198 fail_if_not_exists: None,
1199 }
1200 }
1201}
1202
1203impl From<(ChannelId, MessageId)> for MessageReference {
1204 // TODO(next): Remove this
1205 fn from(pair: (ChannelId, MessageId)) -> Self {
1206 Self {
1207 kind: MessageReferenceKind::default(),
1208 message_id: Some(pair.1),
1209 channel_id: pair.0,
1210 guild_id: None,
1211 fail_if_not_exists: None,
1212 }
1213 }
1214}
1215
1216/// [Discord docs](https://discord.com/developers/docs/resources/channel#channel-mention-object).
1217#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1218#[derive(Clone, Debug, Deserialize, Serialize)]
1219#[non_exhaustive]
1220pub struct ChannelMention {
1221 /// ID of the channel.
1222 pub id: ChannelId,
1223 /// ID of the guild containing the channel.
1224 pub guild_id: GuildId,
1225 /// The kind of channel
1226 #[serde(rename = "type")]
1227 pub kind: ChannelType,
1228 /// The name of the channel
1229 pub name: String,
1230}
1231
1232bitflags! {
1233 /// Describes extra features of the message.
1234 ///
1235 /// [Discord docs](https://discord.com/developers/docs/resources/channel#message-object-message-flags).
1236 #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1237 #[derive(Copy, Clone, Default, Debug, Eq, Hash, PartialEq)]
1238 pub struct MessageFlags: u64 {
1239 /// This message has been published to subscribed channels (via Channel Following).
1240 const CROSSPOSTED = 1 << 0;
1241 /// This message originated from a message in another channel (via Channel Following).
1242 const IS_CROSSPOST = 1 << 1;
1243 /// Do not include any embeds when serializing this message.
1244 const SUPPRESS_EMBEDS = 1 << 2;
1245 /// The source message for this crosspost has been deleted (via Channel Following).
1246 const SOURCE_MESSAGE_DELETED = 1 << 3;
1247 /// This message came from the urgent message system.
1248 const URGENT = 1 << 4;
1249 /// This message has an associated thread, with the same id as the message.
1250 const HAS_THREAD = 1 << 5;
1251 /// This message is only visible to the user who invoked the Interaction.
1252 const EPHEMERAL = 1 << 6;
1253 /// This message is an Interaction Response and the bot is "thinking".
1254 const LOADING = 1 << 7;
1255 /// This message failed to mention some roles and add their members to the thread.
1256 const FAILED_TO_MENTION_SOME_ROLES_IN_THREAD = 1 << 8;
1257 /// This message will not trigger push and desktop notifications.
1258 const SUPPRESS_NOTIFICATIONS = 1 << 12;
1259 /// This message is a voice message.
1260 ///
1261 /// Voice messages have the following properties:
1262 /// - They cannot be edited.
1263 /// - Only a single audio attachment is allowed. No content, stickers, etc...
1264 /// - The [`Attachment`] has additional fields: `duration_secs` and `waveform`.
1265 ///
1266 /// As of 2023-04-14, clients upload a 1 channel, 48000 Hz, 32kbps Opus stream in an OGG container.
1267 /// The encoding is a Discord implementation detail and may change without warning or documentation.
1268 ///
1269 /// As of 2023-04-20, bots are currently not able to send voice messages
1270 /// ([source](https://github.com/discord/discord-api-docs/pull/6082)).
1271 const IS_VOICE_MESSAGE = 1 << 13;
1272 }
1273}
1274
1275#[cfg(feature = "model")]
1276impl MessageId {
1277 /// Returns a link referencing this message. When clicked, users will jump to the message. The
1278 /// link will be valid for messages in either private channels or guilds.
1279 #[must_use]
1280 pub fn link(&self, channel_id: ChannelId, guild_id: Option<GuildId>) -> String {
1281 if let Some(guild_id) = guild_id {
1282 format!("https://discord.com/channels/{guild_id}/{channel_id}/{self}")
1283 } else {
1284 format!("https://discord.com/channels/@me/{channel_id}/{self}")
1285 }
1286 }
1287
1288 /// Same as [`Self::link`] but tries to find the [`GuildId`] if it is not provided.
1289 #[deprecated = "Use GuildChannel::guild_id if you have no GuildId"]
1290 pub async fn link_ensured(
1291 &self,
1292 cache_http: impl CacheHttp,
1293 channel_id: ChannelId,
1294 mut guild_id: Option<GuildId>,
1295 ) -> String {
1296 if guild_id.is_none() {
1297 let found_channel = channel_id.to_channel(cache_http).await;
1298
1299 if let Ok(channel) = found_channel {
1300 if let Some(c) = channel.guild() {
1301 guild_id = Some(c.guild_id);
1302 }
1303 }
1304 }
1305
1306 self.link(channel_id, guild_id)
1307 }
1308}
1309
1310#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1311#[derive(Clone, Debug, Serialize)]
1312#[serde(untagged)]
1313pub enum Nonce {
1314 String(String),
1315 Number(u64),
1316}
1317
1318impl<'de> serde::Deserialize<'de> for Nonce {
1319 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
1320 Ok(StrOrInt::deserialize(deserializer)?.into_enum(Self::String, Self::Number))
1321 }
1322}
1323
1324/// [Discord docs](https://discord.com/developers/docs/resources/channel#role-subscription-data-object)
1325#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1326#[derive(Clone, Debug, Deserialize, Serialize)]
1327pub struct RoleSubscriptionData {
1328 /// The id of the sku and listing that the user is subscribed to.
1329 pub role_subscription_listing_id: SkuId,
1330 /// The name of the tier that the user is subscribed to.
1331 pub tier_name: String,
1332 /// The cumulative number of months that the user has been subscribed for.
1333 pub total_months_subscribed: u16,
1334 /// Whether this notification is for a renewal rather than a new purchase.
1335 pub is_renewal: bool,
1336}
1337
1338/// A poll that has been attached to a [`Message`].
1339///
1340/// [Discord docs](https://discord.com/developers/docs/resources/poll#poll-object)
1341#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1342#[derive(Clone, Debug, Deserialize, Serialize)]
1343#[non_exhaustive]
1344pub struct Poll {
1345 pub question: PollMedia,
1346 pub answers: Vec<PollAnswer>,
1347 pub expiry: Option<Timestamp>,
1348 pub allow_multiselect: bool,
1349 pub layout_type: PollLayoutType,
1350 /// The results of the Poll.
1351 ///
1352 /// None does **not** mean that there are no results, simply that Discord has not provide them.
1353 /// See the discord docs for a more detailed explaination.
1354 pub results: Option<PollResults>,
1355}
1356
1357/// A piece of data used in mutliple parts of the [`Poll`] structure.
1358///
1359/// Currently holds text and an optional emoji, but this is expected to change in future
1360///
1361/// [Discord docs](https://discord.com/developers/docs/resources/poll#poll-media-object)
1362#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1363#[derive(Clone, Debug, Default, Deserialize, Serialize)]
1364#[non_exhaustive]
1365pub struct PollMedia {
1366 pub text: Option<String>,
1367 pub emoji: Option<PollMediaEmoji>,
1368}
1369
1370/// The "Partial Emoji" attached to a [`PollMedia`] model.
1371///
1372/// [Discord docs](https://discord.com/developers/docs/resources/poll#poll-media-object)
1373#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1374#[derive(Clone, Debug, Serialize)]
1375#[serde(rename_all = "lowercase")]
1376pub enum PollMediaEmoji {
1377 Name(String),
1378 Id(EmojiId),
1379}
1380
1381impl<'de> serde::Deserialize<'de> for PollMediaEmoji {
1382 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
1383 #[derive(serde::Deserialize)]
1384 struct RawPollMediaEmoji {
1385 name: Option<String>,
1386 id: Option<EmojiId>,
1387 }
1388
1389 let raw = RawPollMediaEmoji::deserialize(deserializer)?;
1390 if let Some(name) = raw.name {
1391 Ok(PollMediaEmoji::Name(name))
1392 } else if let Some(id) = raw.id {
1393 Ok(PollMediaEmoji::Id(id))
1394 } else {
1395 Err(serde::de::Error::duplicate_field("emoji"))
1396 }
1397 }
1398}
1399
1400impl From<String> for PollMediaEmoji {
1401 fn from(value: String) -> Self {
1402 Self::Name(value)
1403 }
1404}
1405
1406impl From<EmojiId> for PollMediaEmoji {
1407 fn from(value: EmojiId) -> Self {
1408 Self::Id(value)
1409 }
1410}
1411
1412/// A possible answer for a [`Poll`].
1413///
1414/// [Discord docs](https://discord.com/developers/docs/resources/poll#poll-answer-object)
1415#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1416#[derive(Clone, Debug, Deserialize, Serialize)]
1417#[non_exhaustive]
1418pub struct PollAnswer {
1419 pub answer_id: AnswerId,
1420 pub poll_media: PollMedia,
1421}
1422
1423enum_number! {
1424 /// Represents the different layouts that a [`Poll`] may have.
1425 ///
1426 /// Currently, there is only the one option.
1427 ///
1428 /// [Discord docs](https://discord.com/developers/docs/resources/poll#layout-type)
1429 #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
1430 #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1431 #[serde(from = "u8", into = "u8")]
1432 #[non_exhaustive]
1433 pub enum PollLayoutType {
1434 #[default]
1435 Default = 1,
1436 _ => Unknown(u8),
1437 }
1438}
1439
1440/// The model for the results of a [`Poll`].
1441///
1442/// If `is_finalized` is `false`, `answer_counts` will be inaccurate due to Discord's scale.
1443///
1444/// [Discord docs](https://discord.com/developers/docs/resources/poll#poll-results-object-poll-results-object-structure)
1445#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1446#[derive(Clone, Debug, Deserialize, Serialize)]
1447#[non_exhaustive]
1448pub struct PollResults {
1449 pub is_finalized: bool,
1450 pub answer_counts: Vec<PollAnswerCount>,
1451}
1452
1453/// The count of a single [`PollAnswer`]'s results.
1454///
1455/// [Discord docs](https://discord.com/developers/docs/resources/poll#poll-results-object-poll-answer-count-object-structure)
1456#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1457#[derive(Clone, Debug, Deserialize, Serialize)]
1458#[non_exhaustive]
1459pub struct PollAnswerCount {
1460 pub id: AnswerId,
1461 pub count: u64,
1462 pub me_voted: bool,
1463}
1464
1465// all tests here require cache, move if non-cache test is added
1466#[cfg(all(test, feature = "cache"))]
1467mod tests {
1468 use std::collections::HashMap;
1469
1470 use dashmap::DashMap;
1471
1472 use super::{
1473 Guild,
1474 GuildChannel,
1475 Member,
1476 Message,
1477 PermissionOverwrite,
1478 PermissionOverwriteType,
1479 Permissions,
1480 User,
1481 UserId,
1482 };
1483 use crate::cache::wrappers::MaybeMap;
1484 use crate::cache::Cache;
1485
1486 /// Test that author_permissions checks the permissions in a channel, not just the guild.
1487 #[test]
1488 fn author_permissions_respects_overwrites() {
1489 // Author of the message, with a random ID that won't collide with defaults.
1490 let author = User {
1491 id: UserId::new(50778944701071),
1492 ..Default::default()
1493 };
1494
1495 // Channel with the message, with SEND_MESSAGES on.
1496 let channel = GuildChannel {
1497 permission_overwrites: vec![PermissionOverwrite {
1498 allow: Permissions::SEND_MESSAGES,
1499 deny: Permissions::default(),
1500 kind: PermissionOverwriteType::Member(author.id),
1501 }],
1502 ..Default::default()
1503 };
1504 let channel_id = channel.id;
1505
1506 // Guild with the author and channel cached, default (empty) permissions.
1507 let guild = Guild {
1508 channels: HashMap::from([(channel.id, channel)]),
1509 members: HashMap::from([(author.id, Member {
1510 user: author.clone(),
1511 ..Default::default()
1512 })]),
1513 ..Default::default()
1514 };
1515
1516 // Message, tied to the guild and the channel.
1517 let message = Message {
1518 author,
1519 channel_id,
1520 guild_id: Some(guild.id),
1521 ..Default::default()
1522 };
1523
1524 // Cache, with the guild setup.
1525 let mut cache = Cache::new();
1526 cache.guilds = MaybeMap(Some({
1527 let guilds = DashMap::default();
1528 guilds.insert(guild.id, guild);
1529 guilds
1530 }));
1531
1532 // The author should only have the one permission, SEND_MESSAGES.
1533 assert_eq!(message.author_permissions(&cache), Some(Permissions::SEND_MESSAGES));
1534 }
1535}