twilight_cache_any_backend/model/
message.rs

1use twilight_model::{
2    channel::{
3        embed::{Embed, EmbedField},
4        message::{
5            sticker::{MessageSticker, StickerFormatType},
6            MessageActivityType, MessageFlags, MessageType,
7        },
8        Attachment, Message,
9    },
10    gateway::payload::incoming::MessageUpdate,
11    id::{
12        marker::{
13            ApplicationMarker, AttachmentMarker, ChannelMarker, GenericMarker, GuildMarker,
14            MessageMarker, StickerMarker, UserMarker, WebhookMarker,
15        },
16        Id,
17    },
18    util::{ImageHash, Timestamp},
19};
20
21use crate::unique_id;
22
23/// A cached embed field
24///
25/// It's the same as [`twilight_model::channel::embed::EmbedField`] except:
26///
27/// - `embed_id` field is added, making it possible to return an embed's fields
28#[derive(Clone, Debug)]
29pub struct CachedEmbedField {
30    pub embed_id: Id<GenericMarker>,
31    pub inline: bool,
32    pub name: String,
33    pub value: String,
34}
35
36impl CachedEmbedField {
37    /// Create a cached embed field from a given embed field and embed ID
38    #[allow(clippy::missing_const_for_fn)]
39    #[must_use]
40    pub fn from_embed_field(embed_field: EmbedField, embed_id: Id<GenericMarker>) -> Self {
41        Self {
42            embed_id,
43            inline: embed_field.inline,
44            name: embed_field.name,
45            value: embed_field.value,
46        }
47    }
48}
49
50/// A cached embed
51///
52/// It's the same as [`twilight_model::channel::embed::Embed`] except:
53///
54/// - `fields` field is removed and `id` field is added, making it possible to
55///   return an embed's fields
56///
57/// - `message_id` field is added, making it possible to return a message's
58///   embeds
59///
60/// - `author`, `footer`, `image`, `provider`, `thumbnail` and `video` fields
61///   are flattened, making this struct easier to cache
62#[derive(Clone, Debug)]
63pub struct CachedEmbed {
64    pub id: Id<GenericMarker>,
65    pub message_id: Id<MessageMarker>,
66    pub author_icon_url: Option<String>,
67    pub author_name: Option<String>,
68    pub author_proxy_icon_url: Option<String>,
69    pub author_url: Option<String>,
70    pub color: Option<u32>,
71    pub description: Option<String>,
72    pub footer_icon_url: Option<String>,
73    pub footer_proxy_icon_url: Option<String>,
74    pub footer_text: Option<String>,
75    pub image_height: Option<u64>,
76    pub image_proxy_url: Option<String>,
77    pub image_url: Option<String>,
78    pub image_width: Option<u64>,
79    pub kind: String,
80    pub provider_name: Option<String>,
81    pub provider_url: Option<String>,
82    pub thumbnail_height: Option<u64>,
83    pub thumbnail_proxy_url: Option<String>,
84    pub thumbnail_url: Option<String>,
85    pub thumbnail_width: Option<u64>,
86    pub timestamp: Option<Timestamp>,
87    pub title: Option<String>,
88    pub url: Option<String>,
89    pub video_height: Option<u64>,
90    pub video_proxy_url: Option<String>,
91    pub video_url: Option<String>,
92    pub video_width: Option<u64>,
93}
94
95impl CachedEmbed {
96    /// Create a cached embed from a given embed and message ID
97    #[allow(clippy::cast_sign_loss, clippy::as_conversions)]
98    #[must_use]
99    pub fn from_embed(embed: Embed, message_id: Id<MessageMarker>) -> Self {
100        Self {
101            id: Id::new(unique_id() as u64),
102            message_id,
103            author_icon_url: embed
104                .author
105                .as_ref()
106                .and_then(|author| author.icon_url.clone()),
107            author_name: embed.author.as_ref().map(|author| author.name.clone()),
108            author_proxy_icon_url: embed
109                .author
110                .as_ref()
111                .and_then(|author| author.icon_url.clone()),
112            author_url: embed.author.as_ref().and_then(|author| author.url.clone()),
113            color: embed.color,
114            description: embed.description,
115            footer_icon_url: embed
116                .footer
117                .as_ref()
118                .and_then(|footer| footer.icon_url.clone()),
119            footer_proxy_icon_url: embed
120                .footer
121                .as_ref()
122                .and_then(|footer| footer.proxy_icon_url.clone()),
123            footer_text: embed.footer.as_ref().map(|footer| footer.text.clone()),
124            image_height: embed.image.as_ref().and_then(|image| image.height),
125            image_proxy_url: embed
126                .image
127                .as_ref()
128                .and_then(|image| image.proxy_url.clone()),
129            image_url: embed.image.as_ref().map(|image| image.url.clone()),
130            image_width: embed.image.as_ref().and_then(|image| image.width),
131            kind: embed.kind,
132            provider_name: embed
133                .provider
134                .as_ref()
135                .and_then(|provider| provider.name.clone()),
136            provider_url: embed
137                .provider
138                .as_ref()
139                .and_then(|provider| provider.url.clone()),
140            thumbnail_height: embed
141                .thumbnail
142                .as_ref()
143                .and_then(|thumbnail| thumbnail.height),
144            thumbnail_proxy_url: embed
145                .thumbnail
146                .as_ref()
147                .and_then(|thumbnail| thumbnail.proxy_url.clone()),
148            thumbnail_url: embed
149                .thumbnail
150                .as_ref()
151                .map(|thumbnail| thumbnail.url.clone()),
152            thumbnail_width: embed
153                .thumbnail
154                .as_ref()
155                .and_then(|thumbnail| thumbnail.width),
156            timestamp: embed.timestamp,
157            title: embed.title,
158            url: embed.url,
159            video_height: embed.video.as_ref().and_then(|video| video.height),
160            video_proxy_url: embed
161                .video
162                .as_ref()
163                .and_then(|video| video.proxy_url.clone()),
164            video_url: embed.video.as_ref().and_then(|video| video.url.clone()),
165            video_width: embed.video.as_ref().and_then(|video| video.width),
166        }
167    }
168}
169
170/// A cached attachment
171///
172/// It's the same as [`twilight_model::channel::Attachment`] except:
173///
174/// - `message_id` field is added, making it possible to return a message's
175///   attachments
176#[derive(Clone, Debug)]
177pub struct CachedAttachment {
178    pub message_id: Id<MessageMarker>,
179    pub content_type: Option<String>,
180    pub ephemeral: bool,
181    pub filename: String,
182    pub description: Option<String>,
183    pub height: Option<u64>,
184    pub id: Id<AttachmentMarker>,
185    pub proxy_url: String,
186    pub size: u64,
187    pub url: String,
188    pub width: Option<u64>,
189}
190
191impl CachedAttachment {
192    /// Create a cached attachment from a given attachment and message ID
193    #[allow(clippy::missing_const_for_fn)]
194    #[must_use]
195    pub fn from_attachment(attachment: Attachment, message_id: Id<MessageMarker>) -> Self {
196        Self {
197            message_id,
198            content_type: attachment.content_type,
199            ephemeral: attachment.ephemeral,
200            filename: attachment.filename,
201            description: attachment.description,
202            height: attachment.height,
203            id: attachment.id,
204            proxy_url: attachment.proxy_url,
205            size: attachment.size,
206            url: attachment.url,
207            width: attachment.width,
208        }
209    }
210}
211
212/// A cached message sticker
213///
214/// It's the same as
215/// [`twilight_model::channel::message::sticker::MessageSticker`] except:
216///
217/// - `message_id` field is added, making it possible to return a message's
218///   stickers
219#[derive(Clone, Debug)]
220pub struct CachedMessageSticker {
221    pub message_id: Id<MessageMarker>,
222    pub format_type: StickerFormatType,
223    pub id: Id<StickerMarker>,
224    pub name: String,
225}
226
227impl CachedMessageSticker {
228    /// Create a cached message sticker from a given sticker and message ID
229    #[allow(clippy::missing_const_for_fn)]
230    #[must_use]
231    pub fn from_sticker(sticker: MessageSticker, message_id: Id<MessageMarker>) -> Self {
232        Self {
233            message_id,
234            format_type: sticker.format_type,
235            id: sticker.id,
236            name: sticker.name,
237        }
238    }
239}
240
241/// A cached message
242///
243/// It's the same as [`twilight_model::channel::message::Message`] except:
244///
245/// - `activity` and `reference` fields are  flattened, making this struct
246///   easier to cache
247///
248/// - `author`, `referenced_message` and `thread` fields are changed to their
249///   IDs, since they're cached separately
250///
251/// - `components`, `interaction`, `mention_channels`, `mention_roles` and
252///   `mentions` fields are removed, as caching them is likely unnecessary, if
253///   you need these fields, please create an issue
254///
255/// - `member`, `reactions`, `attachments`, `embeds` and `sticker_items` fields
256///   are removed, since they are cached separately
257#[derive(Clone, Debug)]
258pub struct CachedMessage {
259    pub activity_type: Option<MessageActivityType>,
260    pub activity_party_id: Option<String>,
261    pub application_cover_image: Option<ImageHash>,
262    pub application_description: Option<String>,
263    pub application_icon: Option<ImageHash>,
264    pub application_id: Option<Id<ApplicationMarker>>,
265    pub application_name: Option<String>,
266    pub interaction_application_id: Option<Id<ApplicationMarker>>,
267    pub author: Id<UserMarker>,
268    pub channel_id: Id<ChannelMarker>,
269    pub content: String,
270    pub edited_timestamp: Option<Timestamp>,
271    pub flags: Option<MessageFlags>,
272    pub guild_id: Option<Id<GuildMarker>>,
273    pub id: Id<MessageMarker>,
274    pub kind: MessageType,
275    pub mention_everyone: bool,
276    pub pinned: bool,
277    pub reference_channel_id: Option<Id<ChannelMarker>>,
278    pub reference_guild_id: Option<Id<GuildMarker>>,
279    pub reference_message_id: Option<Id<MessageMarker>>,
280    pub reference_fail_if_not_exists: Option<bool>,
281    pub referenced_message: Option<Id<MessageMarker>>,
282    pub timestamp: Timestamp,
283    pub thread: Option<Id<ChannelMarker>>,
284    pub tts: bool,
285    pub webhook_id: Option<Id<WebhookMarker>>,
286}
287
288impl CachedMessage {
289    /// Update the cached message with the message update
290    pub fn update(&mut self, message: &MessageUpdate) {
291        if let Some(content) = &message.content {
292            self.content.clone_from(content);
293        }
294        if message.edited_timestamp.is_some() {
295            self.edited_timestamp = message.edited_timestamp;
296        }
297        if let Some(mentions) = message.mention_everyone {
298            self.mention_everyone = mentions;
299        }
300        if let Some(pinned) = message.pinned {
301            self.pinned = pinned;
302        }
303    }
304}
305
306impl From<&Message> for CachedMessage {
307    fn from(message: &Message) -> Self {
308        Self {
309            activity_type: message.activity.as_ref().map(|activity| activity.kind),
310            activity_party_id: message
311                .activity
312                .as_ref()
313                .and_then(|activity| activity.party_id.clone()),
314            application_cover_image: message
315                .application
316                .as_ref()
317                .and_then(|application| application.cover_image),
318            application_description: message
319                .application
320                .as_ref()
321                .map(|application| application.description.clone()),
322            application_icon: message
323                .application
324                .as_ref()
325                .and_then(|application| application.icon),
326            application_id: message
327                .application
328                .as_ref()
329                .map(|application| application.id),
330            application_name: message
331                .application
332                .as_ref()
333                .map(|application| application.name.clone()),
334            interaction_application_id: message.application_id,
335            author: message.author.id,
336            channel_id: message.channel_id,
337            content: message.content.clone(),
338            edited_timestamp: message.edited_timestamp,
339            guild_id: message.guild_id,
340            id: message.id,
341            kind: message.kind,
342            mention_everyone: message.mention_everyone,
343            pinned: message.pinned,
344            reference_channel_id: message
345                .reference
346                .as_ref()
347                .and_then(|reference| reference.channel_id),
348            reference_guild_id: message
349                .reference
350                .as_ref()
351                .and_then(|reference| reference.guild_id),
352            reference_message_id: message
353                .reference
354                .as_ref()
355                .and_then(|reference| reference.message_id),
356            reference_fail_if_not_exists: message
357                .reference
358                .as_ref()
359                .and_then(|reference| reference.fail_if_not_exists),
360            referenced_message: message
361                .referenced_message
362                .as_ref()
363                .map(|reference| reference.id),
364            timestamp: message.timestamp,
365            thread: message.thread.as_ref().map(|thread| thread.id),
366            tts: message.tts,
367            flags: message.flags,
368            webhook_id: message.webhook_id,
369        }
370    }
371}