Skip to main content

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        Attachment, ChannelMention,
8        message::{
9            Component, Embed, Message, MessageActivity, MessageApplication, MessageCall,
10            MessageFlags, MessageInteraction, MessageReference, MessageSnapshot, MessageType,
11            Reaction, RoleSubscriptionData, sticker::MessageSticker,
12        },
13    },
14    guild::PartialMember,
15    id::{
16        Id,
17        marker::{
18            ApplicationMarker, ChannelMarker, GuildMarker, InteractionMarker, MessageMarker,
19            RoleMarker, UserMarker, WebhookMarker,
20        },
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    /// Information about the call associated with this message.
168    pub const fn call(&self) -> Option<&MessageCall> {
169        self.call.as_ref()
170    }
171
172    /// ID of the channel the message was sent in.
173    pub const fn channel_id(&self) -> Id<ChannelMarker> {
174        self.channel_id
175    }
176
177    /// List of provided components, such as buttons.
178    ///
179    /// Refer to the documentation for [`Message::components`] for caveats with
180    /// receiving the components of messages.
181    ///
182    /// [`Message::components`]: twilight_model::channel::Message::components
183    #[allow(clippy::missing_const_for_fn)]
184    pub fn components(&self) -> &[Component] {
185        &self.components
186    }
187
188    /// Content of a message.
189    ///
190    /// Refer to the documentation for [`Message::content`] for caveats with
191    /// receiving the content of messages.
192    ///
193    /// [`Message::content`]: twilight_model::channel::Message::content
194    #[allow(clippy::missing_const_for_fn)]
195    pub fn content(&self) -> &str {
196        &self.content
197    }
198
199    /// [`Timestamp`] of the date the message was last edited.
200    pub const fn edited_timestamp(&self) -> Option<Timestamp> {
201        self.edited_timestamp
202    }
203
204    /// List of embeds.
205    ///
206    /// Refer to the documentation for [`Message::embeds`] for caveats with
207    /// receiving the embeds of messages.
208    ///
209    /// [`Message::embeds`]: twilight_model::channel::Message::embeds
210    #[allow(clippy::missing_const_for_fn)]
211    pub fn embeds(&self) -> &[Embed] {
212        &self.embeds
213    }
214
215    /// Message flags.
216    pub const fn flags(&self) -> Option<MessageFlags> {
217        self.flags
218    }
219
220    /// ID of the guild the message was sent in, if there is one.
221    pub const fn guild_id(&self) -> Option<Id<GuildMarker>> {
222        self.guild_id
223    }
224
225    /// ID of the message.
226    pub const fn id(&self) -> Id<MessageMarker> {
227        self.id
228    }
229
230    /// Information about the message interaction.
231    pub const fn interaction(&self) -> Option<&CachedMessageInteraction> {
232        self.interaction.as_ref()
233    }
234
235    /// Type of the message.
236    pub const fn kind(&self) -> MessageType {
237        self.kind
238    }
239
240    /// Member data for the author, if there is any.
241    pub const fn member(&self) -> Option<&PartialMember> {
242        self.member.as_ref()
243    }
244
245    /// Channels mentioned in the content.
246    #[allow(clippy::missing_const_for_fn)]
247    pub fn mention_channels(&self) -> &[ChannelMention] {
248        &self.mention_channels
249    }
250
251    /// Whether or not '@everyone' or '@here' is mentioned in the content.
252    pub const fn mention_everyone(&self) -> bool {
253        self.mention_everyone
254    }
255
256    /// Roles mentioned in the content.
257    #[allow(clippy::missing_const_for_fn)]
258    pub fn mention_roles(&self) -> &[Id<RoleMarker>] {
259        &self.mention_roles
260    }
261
262    /// Users mentioned in the content.
263    #[allow(clippy::missing_const_for_fn)]
264    pub fn mentions(&self) -> &[Id<UserMarker>] {
265        &self.mentions
266    }
267
268    /// Snapshots associated with the [`Message::reference`].
269    ///
270    /// [`Message::reference`]: twilight_model::channel::Message::reference
271    pub fn message_snapshots(&self) -> &[MessageSnapshot] {
272        &self.message_snapshots
273    }
274
275    /// Whether or not the message is pinned.
276    pub const fn pinned(&self) -> bool {
277        self.pinned
278    }
279
280    /// Reactions to the message.
281    #[allow(clippy::missing_const_for_fn)]
282    pub fn reactions(&self) -> &[Reaction] {
283        &self.reactions
284    }
285
286    /// Message reference.
287    pub const fn reference(&self) -> Option<&MessageReference> {
288        self.reference.as_ref()
289    }
290
291    /// Information about the role subscription purchase or renewal that
292    /// prompted this message.
293    pub const fn role_subscription_data(&self) -> Option<&RoleSubscriptionData> {
294        self.role_subscription_data.as_ref()
295    }
296
297    /// Stickers within the message.
298    #[allow(clippy::missing_const_for_fn)]
299    pub fn sticker_items(&self) -> &[MessageSticker] {
300        &self.sticker_items
301    }
302
303    /// ID of the thread the message was sent in.
304    pub const fn thread_id(&self) -> Option<Id<ChannelMarker>> {
305        self.thread_id
306    }
307
308    /// [`Timestamp`] of the date the message was sent.
309    pub const fn timestamp(&self) -> Timestamp {
310        self.timestamp
311    }
312
313    /// Whether the message is text-to-speech.
314    pub const fn tts(&self) -> bool {
315        self.tts
316    }
317
318    /// For messages sent by webhooks, the webhook ID.
319    pub const fn webhook_id(&self) -> Option<Id<WebhookMarker>> {
320        self.webhook_id
321    }
322}
323
324impl From<Message> for CachedMessage {
325    #[allow(deprecated)]
326    fn from(message: Message) -> Self {
327        let Message {
328            activity,
329            application,
330            application_id,
331            attachments,
332            author,
333            call,
334            channel_id,
335            components,
336            content,
337            edited_timestamp,
338            embeds,
339            flags,
340            guild_id,
341            id,
342            interaction,
343            interaction_metadata: _,
344            kind,
345            member,
346            mention_channels,
347            mention_everyone,
348            mention_roles,
349            mentions,
350            message_snapshots,
351            pinned,
352            poll,
353            reactions,
354            reference,
355            referenced_message: _,
356            role_subscription_data,
357            sticker_items,
358            timestamp,
359            thread,
360            tts,
361            webhook_id,
362        } = message;
363
364        Self {
365            id,
366            activity,
367            application,
368            application_id,
369            attachments,
370            author: author.id,
371            call,
372            channel_id,
373            components,
374            content,
375            edited_timestamp,
376            embeds,
377            flags,
378            guild_id,
379            interaction: interaction.map(CachedMessageInteraction::from_model),
380            kind,
381            member,
382            mention_channels,
383            mention_everyone,
384            mention_roles,
385            mentions: mentions.into_iter().map(|mention| mention.id).collect(),
386            message_snapshots,
387            pinned,
388            poll,
389            reactions,
390            reference,
391            role_subscription_data,
392            sticker_items,
393            thread_id: thread.map(|thread| thread.id),
394            timestamp,
395            tts,
396            webhook_id,
397        }
398    }
399}
400
401impl PartialEq<Message> for CachedMessage {
402    #[allow(deprecated)]
403    fn eq(&self, other: &Message) -> bool {
404        self.id == other.id
405            && self.activity == other.activity
406            && self.application == other.application
407            && self.application_id == other.application_id
408            && self.attachments == other.attachments
409            && self.author == other.author.id
410            && self.call == other.call
411            && self.channel_id == other.channel_id
412            && self.components == other.components
413            && self.content == other.content
414            && self.edited_timestamp == other.edited_timestamp
415            && self.embeds == other.embeds
416            && self.flags == other.flags
417            && self.guild_id == other.guild_id
418            && self
419                .interaction
420                .as_ref()
421                .map_or(other.interaction.is_none(), |interaction| {
422                    other
423                        .interaction
424                        .as_ref()
425                        .is_some_and(|other_interaction| interaction == other_interaction)
426                })
427            && self.kind == other.kind
428            && self.member == other.member
429            && self.mention_channels == other.mention_channels
430            && self.mention_everyone == other.mention_everyone
431            && self.mention_roles == other.mention_roles
432            && self.mentions.len() == other.mentions.len()
433            && self
434                .mentions
435                .iter()
436                .zip(other.mentions.iter())
437                .all(|(user_id, mention)| user_id == &mention.id)
438            && self.pinned == other.pinned
439            && self.reactions == other.reactions
440            && self.reference == other.reference
441            && self.role_subscription_data == other.role_subscription_data
442            && self.sticker_items == other.sticker_items
443            && self.thread_id == other.thread.as_ref().map(|thread| thread.id)
444            && self.timestamp == other.timestamp
445            && self.tts == other.tts
446            && self.webhook_id == other.webhook_id
447    }
448}
449
450impl CacheableMessage for CachedMessage {
451    fn reactions(&self) -> &[Reaction] {
452        &self.reactions
453    }
454
455    fn reactions_mut(&mut self) -> &mut [Reaction] {
456        &mut self.reactions
457    }
458
459    fn retain_reactions(&mut self, f: impl FnMut(&Reaction) -> bool) {
460        self.reactions.retain(f);
461    }
462
463    fn clear_reactions(&mut self) {
464        self.reactions.clear();
465    }
466
467    fn add_reaction(&mut self, reaction: Reaction) {
468        self.reactions.push(reaction);
469    }
470
471    fn remove_reaction(&mut self, idx: usize) {
472        self.reactions.remove(idx);
473    }
474}
475
476#[cfg(test)]
477mod tests {
478    use super::{CachedMessage, CachedMessageInteraction};
479    use serde::Serialize;
480    use static_assertions::{assert_fields, assert_impl_all};
481    use std::fmt::Debug;
482    use twilight_model::channel::message::Message;
483
484    assert_fields!(
485        CachedMessage: activity,
486        application,
487        application_id,
488        attachments,
489        author,
490        channel_id,
491        components,
492        content,
493        edited_timestamp,
494        embeds,
495        flags,
496        guild_id,
497        id,
498        interaction,
499        kind,
500        member,
501        mention_channels,
502        mention_everyone,
503        mention_roles,
504        mentions,
505        pinned,
506        reactions,
507        reference,
508        sticker_items,
509        thread_id,
510        timestamp,
511        tts,
512        webhook_id
513    );
514    assert_impl_all!(
515        CachedMessage: Clone,
516        Debug,
517        From<Message>,
518        PartialEq,
519        Send,
520        Serialize,
521        Sync,
522    );
523    assert_fields!(CachedMessageInteraction: id, kind, name, user_id);
524    assert_impl_all!(
525        CachedMessageInteraction: Clone,
526        Debug,
527        Eq,
528        PartialEq,
529        Send,
530        Serialize,
531        Sync,
532    );
533}