twilight_cache_inmemory/model/
message.rs

1//! Cached message-related models.
2
3use serde::Serialize;
4use twilight_model::{
5    application::interaction::InteractionType,
6    channel::{
7        message::{
8            sticker::MessageSticker, Component, Embed, Message, MessageActivity,
9            MessageApplication, MessageCall, MessageFlags, MessageInteraction, MessageReference,
10            MessageSnapshot, MessageType, Reaction, RoleSubscriptionData,
11        },
12        Attachment, ChannelMention,
13    },
14    guild::PartialMember,
15    id::{
16        marker::{
17            ApplicationMarker, ChannelMarker, GuildMarker, InteractionMarker, MessageMarker,
18            RoleMarker, UserMarker, WebhookMarker,
19        },
20        Id,
21    },
22    poll::Poll,
23    util::Timestamp,
24};
25
26use crate::CacheableMessage;
27
28/// Information about the message interaction.
29#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
30pub struct CachedMessageInteraction {
31    id: Id<InteractionMarker>,
32    #[serde(rename = "type")]
33    kind: InteractionType,
34    name: String,
35    user_id: Id<UserMarker>,
36}
37
38impl CachedMessageInteraction {
39    /// ID of the interaction.
40    pub const fn id(&self) -> Id<InteractionMarker> {
41        self.id
42    }
43
44    /// Type of the interaction.
45    pub const fn kind(&self) -> InteractionType {
46        self.kind
47    }
48
49    /// Name of the interaction used.
50    #[allow(clippy::missing_const_for_fn)]
51    pub fn name(&self) -> &str {
52        &self.name
53    }
54
55    /// ID of the user who invoked the interaction.
56    pub const fn user_id(&self) -> Id<UserMarker> {
57        self.user_id
58    }
59
60    /// Construct a cached message interaction from its [`twilight_model`] form.
61    #[allow(clippy::missing_const_for_fn)]
62    pub(crate) fn from_model(message_interaction: MessageInteraction) -> Self {
63        // Reasons for dropping fields:
64        //
65        // - `member`: we have the user's ID from the `user_id` field
66        let MessageInteraction {
67            id,
68            kind,
69            member: _,
70            name,
71            user,
72        } = message_interaction;
73
74        Self {
75            id,
76            kind,
77            name,
78            user_id: user.id,
79        }
80    }
81}
82
83impl PartialEq<MessageInteraction> for CachedMessageInteraction {
84    fn eq(&self, other: &MessageInteraction) -> bool {
85        self.id == other.id
86            && self.kind == other.kind
87            && self.name == other.name
88            && self.user_id == other.user.id
89    }
90}
91
92/// Represents a cached [`Message`].
93///
94/// [`Message`]: twilight_model::channel::Message
95#[derive(Clone, Debug, PartialEq, Serialize)]
96pub struct CachedMessage {
97    activity: Option<MessageActivity>,
98    application: Option<MessageApplication>,
99    application_id: Option<Id<ApplicationMarker>>,
100    pub(crate) attachments: Vec<Attachment>,
101    author: Id<UserMarker>,
102    pub(crate) call: Option<MessageCall>,
103    channel_id: Id<ChannelMarker>,
104    components: Vec<Component>,
105    pub(crate) content: String,
106    pub(crate) edited_timestamp: Option<Timestamp>,
107    pub(crate) embeds: Vec<Embed>,
108    flags: Option<MessageFlags>,
109    guild_id: Option<Id<GuildMarker>>,
110    id: Id<MessageMarker>,
111    interaction: Option<CachedMessageInteraction>,
112    kind: MessageType,
113    member: Option<PartialMember>,
114    mention_channels: Vec<ChannelMention>,
115    pub(crate) mention_everyone: bool,
116    pub(crate) mention_roles: Vec<Id<RoleMarker>>,
117    pub(crate) mentions: Vec<Id<UserMarker>>,
118    pub(crate) message_snapshots: Vec<MessageSnapshot>,
119    pub(crate) pinned: bool,
120    pub(crate) poll: Option<Poll>,
121    pub(crate) reactions: Vec<Reaction>,
122    reference: Option<MessageReference>,
123    role_subscription_data: Option<RoleSubscriptionData>,
124    sticker_items: Vec<MessageSticker>,
125    thread_id: Option<Id<ChannelMarker>>,
126    pub(crate) timestamp: Timestamp,
127    pub(crate) tts: bool,
128    webhook_id: Option<Id<WebhookMarker>>,
129}
130
131impl CachedMessage {
132    /// For rich presence chat embeds, the activity object.
133    pub const fn activity(&self) -> Option<&MessageActivity> {
134        self.activity.as_ref()
135    }
136
137    /// For interaction responses, the ID of the interaction's application.
138    pub const fn application(&self) -> Option<&MessageApplication> {
139        self.application.as_ref()
140    }
141
142    /// Associated application's ID.
143    ///
144    /// Sent if the message is a response to an Interaction.
145    pub const fn application_id(&self) -> Option<Id<ApplicationMarker>> {
146        self.application_id
147    }
148
149    /// List of attached files.
150    ///
151    /// Refer to the documentation for [`Message::attachments`] for caveats with
152    /// receiving the attachments of messages.
153    ///
154    /// [`Message::attachments`]: twilight_model::channel::Message::attachments
155    #[allow(clippy::missing_const_for_fn)]
156    pub fn attachments(&self) -> &[Attachment] {
157        &self.attachments
158    }
159
160    /// ID of the message author.
161    ///
162    /// If the author is a webhook, this is its ID.
163    pub const fn author(&self) -> Id<UserMarker> {
164        self.author
165    }
166
167    /// ID of the channel the message was sent in.
168    pub const fn channel_id(&self) -> Id<ChannelMarker> {
169        self.channel_id
170    }
171
172    /// List of provided components, such as buttons.
173    ///
174    /// Refer to the documentation for [`Message::components`] for caveats with
175    /// receiving the components of messages.
176    ///
177    /// [`Message::components`]: twilight_model::channel::Message::components
178    #[allow(clippy::missing_const_for_fn)]
179    pub fn components(&self) -> &[Component] {
180        &self.components
181    }
182
183    /// Content of a message.
184    ///
185    /// Refer to the documentation for [`Message::content`] for caveats with
186    /// receiving the content of messages.
187    ///
188    /// [`Message::content`]: twilight_model::channel::Message::content
189    #[allow(clippy::missing_const_for_fn)]
190    pub fn content(&self) -> &str {
191        &self.content
192    }
193
194    /// [`Timestamp`] of the date the message was last edited.
195    pub const fn edited_timestamp(&self) -> Option<Timestamp> {
196        self.edited_timestamp
197    }
198
199    /// List of embeds.
200    ///
201    /// Refer to the documentation for [`Message::embeds`] for caveats with
202    /// receiving the embeds of messages.
203    ///
204    /// [`Message::embeds`]: twilight_model::channel::Message::embeds
205    #[allow(clippy::missing_const_for_fn)]
206    pub fn embeds(&self) -> &[Embed] {
207        &self.embeds
208    }
209
210    /// Message flags.
211    pub const fn flags(&self) -> Option<MessageFlags> {
212        self.flags
213    }
214
215    /// ID of the guild the message was sent in, if there is one.
216    pub const fn guild_id(&self) -> Option<Id<GuildMarker>> {
217        self.guild_id
218    }
219
220    /// ID of the message.
221    pub const fn id(&self) -> Id<MessageMarker> {
222        self.id
223    }
224
225    /// Information about the message interaction.
226    pub const fn interaction(&self) -> Option<&CachedMessageInteraction> {
227        self.interaction.as_ref()
228    }
229
230    /// Type of the message.
231    pub const fn kind(&self) -> MessageType {
232        self.kind
233    }
234
235    /// Member data for the author, if there is any.
236    pub const fn member(&self) -> Option<&PartialMember> {
237        self.member.as_ref()
238    }
239
240    /// Channels mentioned in the content.
241    #[allow(clippy::missing_const_for_fn)]
242    pub fn mention_channels(&self) -> &[ChannelMention] {
243        &self.mention_channels
244    }
245
246    /// Whether or not '@everyone' or '@here' is mentioned in the content.
247    pub const fn mention_everyone(&self) -> bool {
248        self.mention_everyone
249    }
250
251    /// Roles mentioned in the content.
252    #[allow(clippy::missing_const_for_fn)]
253    pub fn mention_roles(&self) -> &[Id<RoleMarker>] {
254        &self.mention_roles
255    }
256
257    /// Users mentioned in the content.
258    #[allow(clippy::missing_const_for_fn)]
259    pub fn mentions(&self) -> &[Id<UserMarker>] {
260        &self.mentions
261    }
262
263    /// Whether or not the message is pinned.
264    pub const fn pinned(&self) -> bool {
265        self.pinned
266    }
267
268    /// Reactions to the message.
269    #[allow(clippy::missing_const_for_fn)]
270    pub fn reactions(&self) -> &[Reaction] {
271        &self.reactions
272    }
273
274    /// Message reference.
275    pub const fn reference(&self) -> Option<&MessageReference> {
276        self.reference.as_ref()
277    }
278
279    /// Information about the role subscription purchase or renewal that
280    /// prompted this message.
281    pub const fn role_subscription_data(&self) -> Option<&RoleSubscriptionData> {
282        self.role_subscription_data.as_ref()
283    }
284
285    /// Stickers within the message.
286    #[allow(clippy::missing_const_for_fn)]
287    pub fn sticker_items(&self) -> &[MessageSticker] {
288        &self.sticker_items
289    }
290
291    /// ID of the thread the message was sent in.
292    pub const fn thread_id(&self) -> Option<Id<ChannelMarker>> {
293        self.thread_id
294    }
295
296    /// [`Timestamp`] of the date the message was sent.
297    pub const fn timestamp(&self) -> Timestamp {
298        self.timestamp
299    }
300
301    /// Whether the message is text-to-speech.
302    pub const fn tts(&self) -> bool {
303        self.tts
304    }
305
306    /// For messages sent by webhooks, the webhook ID.
307    pub const fn webhook_id(&self) -> Option<Id<WebhookMarker>> {
308        self.webhook_id
309    }
310}
311
312impl From<Message> for CachedMessage {
313    #[allow(deprecated)]
314    fn from(message: Message) -> Self {
315        let Message {
316            activity,
317            application,
318            application_id,
319            attachments,
320            author,
321            call,
322            channel_id,
323            components,
324            content,
325            edited_timestamp,
326            embeds,
327            flags,
328            guild_id,
329            id,
330            interaction,
331            interaction_metadata: _,
332            kind,
333            member,
334            mention_channels,
335            mention_everyone,
336            mention_roles,
337            mentions,
338            message_snapshots,
339            pinned,
340            poll,
341            reactions,
342            reference,
343            referenced_message: _,
344            role_subscription_data,
345            sticker_items,
346            timestamp,
347            thread,
348            tts,
349            webhook_id,
350        } = message;
351
352        Self {
353            id,
354            activity,
355            application,
356            application_id,
357            attachments,
358            author: author.id,
359            call,
360            channel_id,
361            components,
362            content,
363            edited_timestamp,
364            embeds,
365            flags,
366            guild_id,
367            interaction: interaction.map(CachedMessageInteraction::from_model),
368            kind,
369            member,
370            mention_channels,
371            mention_everyone,
372            mention_roles,
373            mentions: mentions.into_iter().map(|mention| mention.id).collect(),
374            message_snapshots,
375            pinned,
376            poll,
377            reactions,
378            reference,
379            role_subscription_data,
380            sticker_items,
381            thread_id: thread.map(|thread| thread.id),
382            timestamp,
383            tts,
384            webhook_id,
385        }
386    }
387}
388
389impl PartialEq<Message> for CachedMessage {
390    #[allow(deprecated)]
391    fn eq(&self, other: &Message) -> bool {
392        self.id == other.id
393            && self.activity == other.activity
394            && self.application == other.application
395            && self.application_id == other.application_id
396            && self.attachments == other.attachments
397            && self.author == other.author.id
398            && self.call == other.call
399            && self.channel_id == other.channel_id
400            && self.components == other.components
401            && self.content == other.content
402            && self.edited_timestamp == other.edited_timestamp
403            && self.embeds == other.embeds
404            && self.flags == other.flags
405            && self.guild_id == other.guild_id
406            && self
407                .interaction
408                .as_ref()
409                .map_or(other.interaction.is_none(), |interaction| {
410                    other
411                        .interaction
412                        .as_ref()
413                        .is_some_and(|other_interaction| interaction == other_interaction)
414                })
415            && self.kind == other.kind
416            && self.member == other.member
417            && self.mention_channels == other.mention_channels
418            && self.mention_everyone == other.mention_everyone
419            && self.mention_roles == other.mention_roles
420            && self.mentions.len() == other.mentions.len()
421            && self
422                .mentions
423                .iter()
424                .zip(other.mentions.iter())
425                .all(|(user_id, mention)| user_id == &mention.id)
426            && self.pinned == other.pinned
427            && self.reactions == other.reactions
428            && self.reference == other.reference
429            && self.role_subscription_data == other.role_subscription_data
430            && self.sticker_items == other.sticker_items
431            && self.thread_id == other.thread.as_ref().map(|thread| thread.id)
432            && self.timestamp == other.timestamp
433            && self.tts == other.tts
434            && self.webhook_id == other.webhook_id
435    }
436}
437
438impl CacheableMessage for CachedMessage {
439    fn reactions(&self) -> &[Reaction] {
440        &self.reactions
441    }
442
443    fn reactions_mut(&mut self) -> &mut [Reaction] {
444        &mut self.reactions
445    }
446
447    fn retain_reactions(&mut self, f: impl FnMut(&Reaction) -> bool) {
448        self.reactions.retain(f);
449    }
450
451    fn clear_reactions(&mut self) {
452        self.reactions.clear();
453    }
454
455    fn add_reaction(&mut self, reaction: Reaction) {
456        self.reactions.push(reaction);
457    }
458
459    fn remove_reaction(&mut self, idx: usize) {
460        self.reactions.remove(idx);
461    }
462}
463
464#[cfg(test)]
465mod tests {
466    use super::{CachedMessage, CachedMessageInteraction};
467    use serde::Serialize;
468    use static_assertions::{assert_fields, assert_impl_all};
469    use std::fmt::Debug;
470    use twilight_model::channel::message::Message;
471
472    assert_fields!(
473        CachedMessage: activity,
474        application,
475        application_id,
476        attachments,
477        author,
478        channel_id,
479        components,
480        content,
481        edited_timestamp,
482        embeds,
483        flags,
484        guild_id,
485        id,
486        interaction,
487        kind,
488        member,
489        mention_channels,
490        mention_everyone,
491        mention_roles,
492        mentions,
493        pinned,
494        reactions,
495        reference,
496        sticker_items,
497        thread_id,
498        timestamp,
499        tts,
500        webhook_id
501    );
502    assert_impl_all!(
503        CachedMessage: Clone,
504        Debug,
505        From<Message>,
506        PartialEq,
507        Send,
508        Serialize,
509        Sync,
510    );
511    assert_fields!(CachedMessageInteraction: id, kind, name, user_id);
512    assert_impl_all!(
513        CachedMessageInteraction: Clone,
514        Debug,
515        Eq,
516        PartialEq,
517        Send,
518        Serialize,
519        Sync,
520    );
521}