Skip to main content

revolt_database/models/messages/
model.rs

1use indexmap::{IndexMap, IndexSet};
2use iso8601_timestamp::Timestamp;
3use revolt_config::{config, FeaturesLimits};
4use revolt_models::v0::{
5    self, BulkMessageResponse, DataMessageSend, Embed, MessageAuthor, MessageFlags, MessageSort,
6    MessageWebhook, PushNotification, ReplyIntent, SendableEmbed, Text,
7};
8use revolt_permissions::{calculate_channel_permissions, ChannelPermission, PermissionValue};
9use revolt_result::{ErrorType, Result};
10use std::time::SystemTime;
11use std::{collections::HashSet, hash::RandomState};
12use ulid::Ulid;
13use validator::Validate;
14
15use crate::{
16    events::client::EventV1,
17    util::{
18        bulk_permissions::BulkDatabasePermissionQuery, idempotency::IdempotencyKey,
19        permissions::DatabasePermissionQuery,
20    },
21    Channel, Database, Emoji, File, User, AMQP,
22};
23
24#[cfg(feature = "tasks")]
25use crate::tasks::{self, ack::AckEvent};
26
27auto_derived_partial!(
28    /// Message
29    pub struct Message {
30        /// Unique Id
31        #[serde(rename = "_id")]
32        pub id: String,
33        /// Unique value generated by client sending this message
34        #[serde(skip_serializing_if = "Option::is_none")]
35        pub nonce: Option<String>,
36        /// Id of the channel this message was sent in
37        pub channel: String,
38        /// Id of the user or webhook that sent this message
39        pub author: String,
40        /// The webhook that sent this message
41        #[serde(skip_serializing_if = "Option::is_none")]
42        pub webhook: Option<MessageWebhook>,
43        /// Message content
44        #[serde(skip_serializing_if = "Option::is_none")]
45        pub content: Option<String>,
46        /// System message
47        #[serde(skip_serializing_if = "Option::is_none")]
48        pub system: Option<SystemMessage>,
49        /// Array of attachments
50        #[serde(skip_serializing_if = "Option::is_none")]
51        pub attachments: Option<Vec<File>>,
52        /// Time at which this message was last edited
53        #[serde(skip_serializing_if = "Option::is_none")]
54        pub edited: Option<Timestamp>,
55        /// Attached embeds to this message
56        #[serde(skip_serializing_if = "Option::is_none")]
57        pub embeds: Option<Vec<Embed>>,
58        /// Array of user ids mentioned in this message
59        #[serde(skip_serializing_if = "Option::is_none")]
60        pub mentions: Option<Vec<String>>,
61        /// Array of role ids mentioned in this message
62        #[serde(skip_serializing_if = "Option::is_none")]
63        pub role_mentions: Option<Vec<String>>,
64        /// Array of message ids this message is replying to
65        #[serde(skip_serializing_if = "Option::is_none")]
66        pub replies: Option<Vec<String>>,
67        /// Hashmap of emoji IDs to array of user IDs
68        #[serde(skip_serializing_if = "IndexMap::is_empty", default)]
69        pub reactions: IndexMap<String, IndexSet<String>>,
70        /// Information about how this message should be interacted with
71        #[serde(skip_serializing_if = "Interactions::is_default", default)]
72        pub interactions: Interactions,
73        /// Name and / or avatar overrides for this message
74        #[serde(skip_serializing_if = "Option::is_none")]
75        pub masquerade: Option<Masquerade>,
76        /// Whether or not the message in pinned
77        #[serde(skip_serializing_if = "crate::if_option_false")]
78        pub pinned: Option<bool>,
79
80        /// Bitfield of message flags
81        #[serde(skip_serializing_if = "Option::is_none")]
82        pub flags: Option<u32>,
83    },
84    "PartialMessage"
85);
86
87auto_derived!(
88    /// System Event
89    #[serde(tag = "type")]
90    pub enum SystemMessage {
91        #[serde(rename = "text")]
92        Text { content: String },
93        #[serde(rename = "user_added")]
94        UserAdded { id: String, by: String },
95        #[serde(rename = "user_remove")]
96        UserRemove { id: String, by: String },
97        #[serde(rename = "user_joined")]
98        UserJoined { id: String },
99        #[serde(rename = "user_left")]
100        UserLeft { id: String },
101        #[serde(rename = "user_kicked")]
102        UserKicked { id: String },
103        #[serde(rename = "user_banned")]
104        UserBanned { id: String },
105        #[serde(rename = "channel_renamed")]
106        ChannelRenamed { name: String, by: String },
107        #[serde(rename = "channel_description_changed")]
108        ChannelDescriptionChanged { by: String },
109        #[serde(rename = "channel_icon_changed")]
110        ChannelIconChanged { by: String },
111        #[serde(rename = "channel_ownership_changed")]
112        ChannelOwnershipChanged { from: String, to: String },
113        #[serde(rename = "message_pinned")]
114        MessagePinned { id: String, by: String },
115        #[serde(rename = "message_unpinned")]
116        MessageUnpinned { id: String, by: String },
117        #[serde(rename = "call_started")]
118        CallStarted {
119            by: String,
120            finished_at: Option<Timestamp>,
121        },
122    }
123
124    /// Name and / or avatar override information
125    pub struct Masquerade {
126        /// Replace the display name shown on this message
127        #[serde(skip_serializing_if = "Option::is_none")]
128        pub name: Option<String>,
129        /// Replace the avatar shown on this message (URL to image file)
130        #[serde(skip_serializing_if = "Option::is_none")]
131        pub avatar: Option<String>,
132        /// Replace the display role colour shown on this message
133        ///
134        /// Must have `ManageRole` permission to use
135        #[serde(skip_serializing_if = "Option::is_none")]
136        pub colour: Option<String>,
137    }
138
139    /// Information to guide interactions on this message
140    #[derive(Default)]
141    pub struct Interactions {
142        /// Reactions which should always appear and be distinct
143        #[serde(skip_serializing_if = "Option::is_none", default)]
144        pub reactions: Option<IndexSet<String>>,
145        /// Whether reactions should be restricted to the given list
146        ///
147        /// Can only be set to true if reactions list is of at least length 1
148        #[serde(skip_serializing_if = "crate::if_false", default)]
149        pub restrict_reactions: bool,
150    }
151
152    /// Appended Information
153    pub struct AppendMessage {
154        /// Additional embeds to include in this message
155        #[serde(skip_serializing_if = "Option::is_none")]
156        pub embeds: Option<Vec<Embed>>,
157    }
158
159    /// Message Time Period
160    ///
161    /// Filter and sort messages by time
162    #[serde(untagged)]
163    pub enum MessageTimePeriod {
164        Relative {
165            /// Message id to search around
166            ///
167            /// Specifying 'nearby' ignores 'before', 'after' and 'sort'.
168            /// It will also take half of limit rounded as the limits to each side.
169            /// It also fetches the message ID specified.
170            nearby: String,
171        },
172        Absolute {
173            /// Message id before which messages should be fetched
174            before: Option<String>,
175            /// Message id after which messages should be fetched
176            after: Option<String>,
177            /// Message sort direction
178            sort: Option<MessageSort>,
179        },
180    }
181
182    /// Message Filter
183    #[derive(Default)]
184    pub struct MessageFilter {
185        /// Parent channel ID
186        pub channel: Option<String>,
187        /// Message author ID
188        pub author: Option<String>,
189        /// Search query
190        pub query: Option<String>,
191        /// Search for pinned
192        pub pinned: Option<bool>,
193    }
194
195    /// Message Query
196    pub struct MessageQuery {
197        /// Maximum number of messages to fetch
198        ///
199        /// For fetching nearby messages, this is \`(limit + 1)\`.
200        pub limit: Option<i64>,
201        /// Filter to apply
202        #[serde(flatten)]
203        pub filter: MessageFilter,
204        /// Time period to fetch
205        #[serde(flatten)]
206        pub time_period: MessageTimePeriod,
207    }
208
209    /// Optional fields on message
210    pub enum FieldsMessage {
211        Pinned,
212    }
213);
214
215pub struct MessageFlagsValue(pub u32);
216
217impl MessageFlagsValue {
218    pub fn has(&self, flag: MessageFlags) -> bool {
219        self.has_value(flag as u32)
220    }
221    pub fn has_value(&self, bit: u32) -> bool {
222        let mask = 1 << bit;
223        self.0 & mask == mask
224    }
225
226    pub fn set(&mut self, flag: MessageFlags, toggle: bool) -> &mut Self {
227        self.set_value(flag as u32, toggle)
228    }
229    pub fn set_value(&mut self, bit: u32, toggle: bool) -> &mut Self {
230        if toggle {
231            self.0 |= 1 << bit;
232        } else {
233            self.0 &= !(1 << bit);
234        }
235        self
236    }
237}
238
239#[allow(clippy::derivable_impls)]
240impl Default for Message {
241    fn default() -> Self {
242        Self {
243            id: Default::default(),
244            nonce: None,
245            channel: Default::default(),
246            author: Default::default(),
247            webhook: None,
248            content: None,
249            system: None,
250            attachments: None,
251            edited: None,
252            embeds: None,
253            mentions: None,
254            role_mentions: None,
255            replies: None,
256            reactions: Default::default(),
257            interactions: Default::default(),
258            masquerade: None,
259            flags: None,
260            pinned: None,
261        }
262    }
263}
264
265#[allow(clippy::disallowed_methods)]
266impl Message {
267    /// Create message from API data
268    #[allow(clippy::too_many_arguments)]
269    pub async fn create_from_api(
270        db: &Database,
271        amqp: Option<&AMQP>,
272        channel: Channel,
273        data: DataMessageSend,
274        author: MessageAuthor<'_>,
275        user: Option<v0::User>,
276        member: Option<v0::Member>,
277        limits: FeaturesLimits,
278        mut idempotency: IdempotencyKey,
279        generate_embeds: bool,
280        allow_mentions: bool,
281    ) -> Result<Message> {
282        let config = config().await;
283
284        Message::validate_sum(
285            &data.content,
286            data.embeds.as_deref().unwrap_or_default(),
287            limits.message_length,
288        )?;
289
290        idempotency
291            .consume_nonce(data.nonce)
292            .await
293            .map_err(|_| create_error!(InvalidOperation))?;
294
295        // Check the message is not empty
296        if (data.content.as_ref().is_none_or(|v| v.is_empty()))
297            && (data.attachments.as_ref().is_none_or(|v| v.is_empty()))
298            && (data.embeds.as_ref().is_none_or(|v| v.is_empty()))
299        {
300            return Err(create_error!(EmptyMessage));
301        }
302
303        let allow_mass_mentions = allow_mentions && config.features.mass_mentions_enabled;
304
305        let mut mentions_everyone = false;
306        let mut mentions_online = false;
307        let mut suppress_notifications = false;
308
309        if let Some(raw_flags) = &data.flags {
310            if raw_flags > &7 {
311                // quick path to failure: bigger than all the bits combined
312                return Err(create_error!(InvalidProperty));
313            }
314
315            // First step of mass mention resolution
316            let flags = MessageFlagsValue(*raw_flags);
317            suppress_notifications = flags.has(MessageFlags::SuppressNotifications);
318            mentions_everyone = allow_mentions && flags.has(MessageFlags::MentionsEveryone);
319            mentions_online = allow_mentions && flags.has(MessageFlags::MentionsOnline);
320
321            // Not a bot, and attempting to set mention flags
322            if user.as_ref().is_some_and(|u| u.bot.as_ref().is_none())
323                && (mentions_everyone || mentions_online)
324            {
325                return Err(create_error!(IsNotBot));
326            }
327
328            if mentions_everyone && mentions_online {
329                return Err(create_error!(InvalidFlagValue));
330            }
331        }
332
333        let server_id = match channel {
334            Channel::TextChannel { ref server, .. } => Some(server.clone()),
335            _ => None,
336        };
337
338        // Ensure restrict_reactions is not specified without reactions list
339        if let Some(interactions) = &data.interactions {
340            if interactions.restrict_reactions {
341                let disallowed = if let Some(list) = &interactions.reactions {
342                    list.is_empty()
343                } else {
344                    true
345                };
346
347                if disallowed {
348                    return Err(create_error!(InvalidProperty));
349                }
350            }
351        }
352
353        let (author_id, webhook) = match &author {
354            MessageAuthor::User(user) => (user.id.clone(), None),
355            MessageAuthor::Webhook(webhook) => (webhook.id.clone(), Some((*webhook).clone())),
356            MessageAuthor::System { .. } => ("00000000000000000000000000".to_string(), None),
357        };
358
359        // Start constructing the message
360        let message_id = Ulid::new().to_string();
361        let mut message = Message {
362            id: message_id.clone(),
363            channel: channel.id().to_string(),
364            masquerade: data.masquerade.map(|masquerade| masquerade.into()),
365            interactions: data
366                .interactions
367                .map(|interactions| interactions.into())
368                .unwrap_or_default(),
369            author: author_id,
370            webhook: webhook.map(|w| w.into()),
371            flags: data.flags,
372            ..Default::default()
373        };
374
375        // Parse mentions in message.
376
377        let mut message_mentions = if let Some(raw_content) = &data.content {
378            revolt_parser::parse_message(raw_content)
379        } else {
380            revolt_parser::MessageResults::default()
381        };
382
383        message_mentions.mentions_everyone |= mentions_everyone;
384        message_mentions.mentions_online |= mentions_online;
385
386        let revolt_parser::MessageResults {
387            mut user_mentions,
388            mut role_mentions,
389            mut mentions_everyone,
390            mut mentions_online,
391            ..
392        } = message_mentions;
393
394        if allow_mass_mentions && server_id.is_some() && !role_mentions.is_empty() {
395            let server_data = db
396                .fetch_server(server_id.unwrap().as_str())
397                .await
398                .expect("Failed to fetch server");
399
400            role_mentions.retain(|role_id| server_data.roles.contains_key(role_id));
401        }
402
403        // Validate the user can perform a mass mention
404        if !config.features.mass_mentions_enabled
405            && (mentions_everyone || mentions_online || !role_mentions.is_empty())
406        {
407            mentions_everyone = false;
408            mentions_online = false;
409            role_mentions.clear();
410        } else if mentions_everyone || mentions_online || !role_mentions.is_empty() {
411            debug!(
412                "Mentioned everyone: {}, mentioned online: {}, mentioned roles: {:?}",
413                mentions_everyone, mentions_online, &role_mentions
414            );
415            if let Some(user) = match author {
416                MessageAuthor::User(user) => Some(Ok(user)),
417                MessageAuthor::System { .. } => Some(Err(())), // DISALLOWED
418                MessageAuthor::Webhook(..) => None,            // Bypass check
419            } {
420                if user.is_err() {
421                    return Err(create_error!(InvalidProperty));
422                }
423                let owned_user: User = user.unwrap().to_owned().into();
424
425                let mut query = DatabasePermissionQuery::new(db, &owned_user).channel(&channel);
426                let perms = calculate_channel_permissions(&mut query).await;
427
428                if (mentions_everyone || mentions_online)
429                    && !perms.has_channel_permission(ChannelPermission::MentionEveryone)
430                {
431                    return Err(create_error!(MissingPermission {
432                        permission: ChannelPermission::MentionEveryone.to_string()
433                    }));
434                }
435
436                if !role_mentions.is_empty()
437                    && !perms.has_channel_permission(ChannelPermission::MentionRoles)
438                {
439                    return Err(create_error!(MissingPermission {
440                        permission: ChannelPermission::MentionRoles.to_string()
441                    }));
442                }
443            }
444        }
445
446        // Verify replies are valid.
447        let mut replies = Vec::new();
448
449        if let Some(entries) = data.replies {
450            if entries.len() > config.features.limits.global.message_replies {
451                return Err(create_error!(TooManyReplies {
452                    max: config.features.limits.global.message_replies,
453                }));
454            }
455
456            replies.reserve(entries.len());
457
458            for ReplyIntent {
459                id,
460                mention,
461                fail_if_not_exists,
462            } in entries
463            {
464                match db.fetch_message(&id).await {
465                    // Referenced message exists
466                    Ok(message) => {
467                        if mention && allow_mentions {
468                            user_mentions.insert(message.author.to_owned());
469                        }
470
471                        // This is O(n^2), but this is faster than a HashSet
472                        // when n < 20; as long as the message_replies limit
473                        // is reasonable, this will be fast.
474                        if !replies.contains(&message.id) {
475                            replies.push(message.id);
476                        }
477                    }
478                    // If the referenced message doesn't exist and fail_if_not_exists
479                    // is set to false, send the message without the reply.
480                    Err(e) => {
481                        if !matches!(e.error_type, ErrorType::NotFound)
482                            || fail_if_not_exists.unwrap_or(true)
483                        {
484                            return Err(e);
485                        }
486                    }
487                }
488            }
489        }
490
491        // Validate the mentions go to users in the channel/server
492        if !user_mentions.is_empty() {
493            #[allow(deprecated)]
494            match channel {
495                Channel::DirectMessage { ref recipients, .. }
496                | Channel::Group { ref recipients, .. } => {
497                    let recipients_hash = HashSet::<&String, RandomState>::from_iter(recipients);
498                    user_mentions.retain(|m| recipients_hash.contains(m));
499                    role_mentions.clear();
500                }
501                Channel::TextChannel { ref server, .. } => {
502                    let mentions_vec = Vec::from_iter(user_mentions.iter().cloned());
503
504                    let valid_members = db.fetch_members(server.as_str(), &mentions_vec[..]).await;
505                    if let Ok(valid_members) = valid_members {
506                        let valid_mentions = HashSet::<&String, RandomState>::from_iter(
507                            valid_members.iter().map(|m| &m.id.user),
508                        );
509
510                        user_mentions.retain(|m| valid_mentions.contains(m)); // quick pass, validate mentions are in the server
511
512                        if !user_mentions.is_empty() {
513                            // if there are still mentions, drill down to a channel-level
514                            let member_channel_view_perms =
515                                BulkDatabasePermissionQuery::from_server_id(db, server)
516                                    .await
517                                    .channel(&channel)
518                                    .members(&valid_members)
519                                    .members_can_see_channel()
520                                    .await;
521
522                            user_mentions
523                                .retain(|m| *member_channel_view_perms.get(m).unwrap_or(&false));
524                        }
525                    } else {
526                        revolt_config::capture_error(&valid_members.unwrap_err());
527                        return Err(create_error!(InternalError));
528                    }
529                }
530                Channel::SavedMessages { .. } => {
531                    user_mentions.clear();
532                }
533            }
534        }
535
536        if !user_mentions.is_empty() {
537            message
538                .mentions
539                .replace(user_mentions.into_iter().collect());
540        }
541
542        if !role_mentions.is_empty() {
543            message
544                .role_mentions
545                .replace(role_mentions.into_iter().collect());
546        }
547
548        if !replies.is_empty() {
549            message.replies.replace(replies);
550        }
551
552        // Calculate final message flags
553        let mut flag_value = MessageFlagsValue(0);
554        flag_value
555            .set(MessageFlags::SuppressNotifications, suppress_notifications)
556            .set(MessageFlags::MentionsEveryone, mentions_everyone)
557            .set(MessageFlags::MentionsOnline, mentions_online);
558
559        message.flags = Some(flag_value.0);
560
561        // Add attachments to message.
562        let mut attachments = vec![];
563        if data
564            .attachments
565            .as_ref()
566            .is_some_and(|v| v.len() > limits.message_attachments)
567        {
568            return Err(create_error!(TooManyAttachments {
569                max: limits.message_attachments,
570            }));
571        }
572
573        if data
574            .embeds
575            .as_ref()
576            .is_some_and(|v| v.len() > config.features.limits.global.message_embeds)
577        {
578            return Err(create_error!(TooManyEmbeds {
579                max: config.features.limits.global.message_embeds,
580            }));
581        }
582
583        for attachment_id in data.attachments.as_deref().unwrap_or_default() {
584            attachments
585                .push(File::use_attachment(db, attachment_id, &message_id, author.id()).await?);
586        }
587
588        if !attachments.is_empty() {
589            message.attachments.replace(attachments);
590        }
591
592        // Process included embeds.
593        for sendable_embed in data.embeds.unwrap_or_default() {
594            message.attach_sendable_embed(db, sendable_embed).await?;
595        }
596
597        // Set content
598        message.content = data.content;
599
600        // Pass-through nonce value for clients
601        message.nonce = Some(idempotency.into_key());
602
603        // Send the message
604        message
605            .send(db, amqp, author, user, member, &channel, generate_embeds)
606            .await?;
607
608        Ok(message)
609    }
610
611    /// Send a message without any notifications
612    pub async fn send_without_notifications(
613        &mut self,
614        db: &Database,
615        user: Option<v0::User>,
616        member: Option<v0::Member>,
617        is_dm: bool,
618        generate_embeds: bool,
619        // This determines if this function should queue the mentions task or if somewhere else will.
620        // If this is true, you MUST call tasks::ack::queue yourself.
621        mentions_elsewhere: bool,
622    ) -> Result<()> {
623        db.insert_message(self).await?;
624
625        // Fan out events
626        EventV1::Message(self.clone().into_model(user, member))
627            .p(self.channel.to_string())
628            .await;
629
630        // Update last_message_id
631        #[cfg(feature = "tasks")]
632        tasks::last_message_id::queue(self.channel.to_string(), self.id.to_string(), is_dm).await;
633
634        // Add mentions for affected users
635        #[cfg(feature = "tasks")]
636        if !mentions_elsewhere {
637            if let Some(mentions) = &self.mentions {
638                tasks::ack::queue_message(
639                    self.channel.to_string(),
640                    AckEvent::ProcessMessage {
641                        messages: vec![(
642                            None,
643                            self.clone(),
644                            mentions.clone(),
645                            self.has_suppressed_notifications(),
646                        )],
647                    },
648                )
649                .await;
650            }
651        }
652
653        // Generate embeds
654        #[cfg(feature = "tasks")]
655        if generate_embeds {
656            if let Some(content) = &self.content {
657                tasks::process_embeds::queue(
658                    self.channel.to_string(),
659                    self.id.to_string(),
660                    content.clone(),
661                )
662                .await;
663            }
664        }
665
666        Ok(())
667    }
668
669    /// Send a message
670    #[allow(clippy::too_many_arguments)]
671    pub async fn send(
672        &mut self,
673        db: &Database,
674        _amqp: Option<&AMQP>, // this is optional mostly for tests.
675        author: MessageAuthor<'_>,
676        user: Option<v0::User>,
677        member: Option<v0::Member>,
678        channel: &Channel,
679        generate_embeds: bool,
680    ) -> Result<()> {
681        self.send_without_notifications(
682            db,
683            user.clone(),
684            member.clone(),
685            matches!(channel, Channel::DirectMessage { .. }),
686            generate_embeds,
687            true,
688        )
689        .await?;
690
691        let is_dm_or_group = matches!(
692            channel,
693            Channel::DirectMessage { .. } | Channel::Group { .. }
694        );
695
696        if !self.has_suppressed_notifications()
697            && (is_dm_or_group || self.mentions.is_some() || self.contains_mass_push_mention())
698        {
699            // send Push notifications
700            #[cfg(feature = "tasks")]
701            tasks::ack::queue_message(
702                self.channel.to_string(),
703                AckEvent::ProcessMessage {
704                    messages: vec![(
705                        Some(
706                            PushNotification::from(
707                                self.clone().into_model(user, member),
708                                Some(author.clone()),
709                                channel.to_owned().into(),
710                            )
711                            .await,
712                        ),
713                        self.clone(),
714                        match channel {
715                            Channel::DirectMessage { recipients, .. }
716                            | Channel::Group { recipients, .. } => recipients
717                                .iter()
718                                .filter(|uid| *uid != author.id())
719                                .cloned()
720                                .collect(),
721                            Channel::TextChannel { .. } => {
722                                self.mentions.clone().unwrap_or_default()
723                            }
724                            _ => vec![],
725                        },
726                        false, // branch already dictates this
727                    )],
728                },
729            )
730            .await;
731        }
732
733        Ok(())
734    }
735
736    /// Create text embed from sendable embed
737    pub async fn create_embed(&self, db: &Database, embed: SendableEmbed) -> Result<Embed> {
738        embed.validate().map_err(|error| {
739            create_error!(FailedValidation {
740                error: error.to_string()
741            })
742        })?;
743
744        let media = if let Some(id) = embed.media {
745            Some(File::use_attachment(db, &id, &self.id, &self.author).await?)
746        } else {
747            None
748        };
749
750        Ok(Embed::Text(Text {
751            icon_url: embed.icon_url,
752            url: embed.url,
753            title: embed.title,
754            description: embed.description,
755            media: media.map(|m| m.into()),
756            colour: embed.colour,
757        }))
758    }
759
760    /// Whether this message has suppressed notifications
761    pub fn has_suppressed_notifications(&self) -> bool {
762        if let Some(flags) = self.flags {
763            flags & MessageFlags::SuppressNotifications as u32
764                == MessageFlags::SuppressNotifications as u32
765        } else {
766            false
767        }
768    }
769
770    pub fn contains_mass_push_mention(&self) -> bool {
771        let ping = if let Some(flags) = self.flags {
772            let flags = MessageFlagsValue(flags);
773            flags.has(MessageFlags::MentionsEveryone)
774        } else {
775            false
776        };
777
778        ping || self.role_mentions.is_some()
779    }
780
781    /// Update message data
782    pub async fn update(
783        &mut self,
784        db: &Database,
785        partial: PartialMessage,
786        remove: Vec<FieldsMessage>,
787    ) -> Result<()> {
788        self.apply_options(partial.clone());
789
790        for field in &remove {
791            self.remove_field(field);
792        }
793
794        db.update_message(&self.id, &partial, remove.clone())
795            .await?;
796
797        EventV1::MessageUpdate {
798            id: self.id.clone(),
799            channel: self.channel.clone(),
800            data: partial.into(),
801            clear: remove.into_iter().map(|field| field.into()).collect(),
802        }
803        .p(self.channel.clone())
804        .await;
805
806        Ok(())
807    }
808
809    /// Helper function to fetch many messages with users
810    pub async fn fetch_with_users(
811        db: &Database,
812        query: MessageQuery,
813        perspective: &User,
814        include_users: Option<bool>,
815        server_id: Option<&str>,
816    ) -> Result<BulkMessageResponse> {
817        let messages: Vec<v0::Message> = db
818            .fetch_messages(query)
819            .await?
820            .into_iter()
821            .map(|msg| msg.into_model(None, None))
822            .collect();
823
824        if let Some(true) = include_users {
825            let user_ids = messages
826                .iter()
827                .flat_map(|m| {
828                    let mut users = vec![m.author.clone()];
829                    if let Some(system) = &m.system {
830                        match system {
831                            v0::SystemMessage::ChannelDescriptionChanged { by } => {
832                                users.push(by.clone())
833                            }
834                            v0::SystemMessage::ChannelIconChanged { by } => users.push(by.clone()),
835                            v0::SystemMessage::ChannelOwnershipChanged { from, to, .. } => {
836                                users.push(from.clone());
837                                users.push(to.clone())
838                            }
839                            v0::SystemMessage::ChannelRenamed { by, .. } => users.push(by.clone()),
840                            v0::SystemMessage::UserAdded { by, id, .. }
841                            | v0::SystemMessage::UserRemove { by, id, .. } => {
842                                users.push(by.clone());
843                                users.push(id.clone());
844                            }
845                            v0::SystemMessage::UserBanned { id, .. }
846                            | v0::SystemMessage::UserKicked { id, .. }
847                            | v0::SystemMessage::UserJoined { id, .. }
848                            | v0::SystemMessage::UserLeft { id, .. } => {
849                                users.push(id.clone());
850                            }
851                            v0::SystemMessage::Text { .. } => {}
852                            v0::SystemMessage::MessagePinned { by, .. } => {
853                                users.push(by.clone());
854                            }
855                            v0::SystemMessage::MessageUnpinned { by, .. } => {
856                                users.push(by.clone());
857                            }
858                            v0::SystemMessage::CallStarted { by, .. } => users.push(by.clone()),
859                        }
860                    }
861                    users
862                })
863                .collect::<HashSet<String>>()
864                .into_iter()
865                .collect::<Vec<String>>();
866            let users = User::fetch_many_ids_as_mutuals(db, perspective, &user_ids).await?;
867
868            Ok(BulkMessageResponse::MessagesAndUsers {
869                messages,
870                users,
871                members: if let Some(server_id) = server_id {
872                    Some(
873                        db.fetch_members(server_id, &user_ids)
874                            .await?
875                            .into_iter()
876                            .map(Into::into)
877                            .collect(),
878                    )
879                } else {
880                    None
881                },
882            })
883        } else {
884            Ok(BulkMessageResponse::JustMessages(messages))
885        }
886    }
887
888    /// Append content to message
889    pub async fn append(
890        db: &Database,
891        id: String,
892        channel: String,
893        append: AppendMessage,
894    ) -> Result<()> {
895        db.append_message(&id, &append).await?;
896
897        EventV1::MessageAppend {
898            id,
899            channel: channel.to_string(),
900            append: append.into(),
901        }
902        .p(channel)
903        .await;
904
905        Ok(())
906    }
907
908    /// Convert sendable embed to text embed and attach to message
909    pub async fn attach_sendable_embed(
910        &mut self,
911        db: &Database,
912        embed: v0::SendableEmbed,
913    ) -> Result<()> {
914        let media: Option<v0::File> = if let Some(id) = embed.media {
915            Some(
916                File::use_attachment(db, &id, &self.id, &self.author)
917                    .await?
918                    .into(),
919            )
920        } else {
921            None
922        };
923
924        let embed = v0::Embed::Text(v0::Text {
925            icon_url: embed.icon_url,
926            url: embed.url,
927            title: embed.title,
928            description: embed.description,
929            media,
930            colour: embed.colour,
931        });
932
933        if let Some(embeds) = &mut self.embeds {
934            embeds.push(embed);
935        } else {
936            self.embeds = Some(vec![embed]);
937        }
938
939        Ok(())
940    }
941
942    /// Add a reaction to a message
943    pub async fn add_reaction(&self, db: &Database, user: &User, emoji: &str) -> Result<()> {
944        // Check how many reactions are already on the message
945        let config = config().await;
946        if self.reactions.len() >= config.features.limits.global.message_reactions
947            && !self.reactions.contains_key(emoji)
948        {
949            return Err(create_error!(InvalidOperation));
950        }
951
952        // Check if the emoji is whitelisted
953        if !self.interactions.can_use(emoji) {
954            return Err(create_error!(InvalidOperation));
955        }
956
957        // Check if the emoji is usable by us
958        if !Emoji::can_use(db, emoji).await? {
959            return Err(create_error!(InvalidOperation));
960        }
961
962        // Send reaction event
963        EventV1::MessageReact {
964            id: self.id.to_string(),
965            channel_id: self.channel.to_string(),
966            user_id: user.id.to_string(),
967            emoji_id: emoji.to_string(),
968        }
969        .p(self.channel.to_string())
970        .await;
971
972        // Add emoji
973        db.add_reaction(&self.id, emoji, &user.id).await
974    }
975
976    /// Validate the sum of content of a message is under threshold
977    pub fn validate_sum(
978        content: &Option<String>,
979        embeds: &[SendableEmbed],
980        max_length: usize,
981    ) -> Result<()> {
982        let mut running_total = 0;
983        if let Some(content) = content {
984            running_total += content.len();
985        }
986
987        for embed in embeds {
988            if let Some(desc) = &embed.description {
989                running_total += desc.len();
990            }
991        }
992
993        if running_total <= max_length {
994            Ok(())
995        } else {
996            Err(create_error!(PayloadTooLarge))
997        }
998    }
999
1000    /// Delete a message
1001    pub async fn delete(self, db: &Database) -> Result<()> {
1002        let file_ids: Vec<String> = self
1003            .attachments
1004            .map(|files| files.iter().map(|file| file.id.to_string()).collect())
1005            .unwrap_or_default();
1006
1007        if !file_ids.is_empty() {
1008            db.mark_attachments_as_deleted(&file_ids).await?;
1009        }
1010
1011        db.delete_message(&self.id).await?;
1012
1013        EventV1::MessageDelete {
1014            id: self.id,
1015            channel: self.channel.clone(),
1016        }
1017        .p(self.channel)
1018        .await;
1019        Ok(())
1020    }
1021
1022    /// Bulk delete messages
1023    pub async fn bulk_delete(db: &Database, channel: &str, ids: Vec<String>) -> Result<()> {
1024        let valid_ids = db
1025            .fetch_messages_by_id(&ids)
1026            .await?
1027            .into_iter()
1028            .filter(|msg| msg.channel == channel)
1029            .map(|msg| msg.id)
1030            .collect::<Vec<String>>();
1031
1032        db.delete_messages(channel, &valid_ids).await?;
1033        EventV1::BulkMessageDelete {
1034            channel: channel.to_string(),
1035            ids: valid_ids,
1036        }
1037        .p(channel.to_string())
1038        .await;
1039        Ok(())
1040    }
1041
1042    /// Bulk delete messages by an author since a given time
1043    pub async fn bulk_delete_by_author_since(
1044        db: &Database,
1045        channels: &[String],
1046        author: &str,
1047        since: SystemTime,
1048    ) -> Result<()> {
1049        let deleted_groups = db
1050            .delete_messages_by_author_since(channels, author, since)
1051            .await?;
1052
1053        for (channel_id, message_ids) in deleted_groups {
1054            if !message_ids.is_empty() {
1055                EventV1::BulkMessageDelete {
1056                    channel: channel_id.clone(),
1057                    ids: message_ids,
1058                }
1059                .p(channel_id)
1060                .await;
1061            }
1062        }
1063
1064        Ok(())
1065    }
1066
1067    /// Remove a reaction from a message
1068    pub async fn remove_reaction(&self, db: &Database, user: &str, emoji: &str) -> Result<()> {
1069        // Check if it actually exists
1070        let empty = if let Some(users) = self.reactions.get(emoji) {
1071            if !users.contains(user) {
1072                return Err(create_error!(NotFound));
1073            }
1074
1075            users.len() == 1
1076        } else {
1077            return Err(create_error!(NotFound));
1078        };
1079
1080        // Send reaction event
1081        EventV1::MessageUnreact {
1082            id: self.id.to_string(),
1083            channel_id: self.channel.to_string(),
1084            user_id: user.to_string(),
1085            emoji_id: emoji.to_string(),
1086        }
1087        .p(self.channel.to_string())
1088        .await;
1089
1090        if empty {
1091            // If empty, remove the reaction entirely
1092            db.clear_reaction(&self.id, emoji).await
1093        } else {
1094            // Otherwise only remove that one reaction
1095            db.remove_reaction(&self.id, emoji, user).await
1096        }
1097    }
1098
1099    /// Remove a reaction from a message
1100    pub async fn clear_reaction(&self, db: &Database, emoji: &str) -> Result<()> {
1101        // Send reaction event
1102        EventV1::MessageRemoveReaction {
1103            id: self.id.to_string(),
1104            channel_id: self.channel.to_string(),
1105            emoji_id: emoji.to_string(),
1106        }
1107        .p(self.channel.to_string())
1108        .await;
1109
1110        // Write to database
1111        db.clear_reaction(&self.id, emoji).await
1112    }
1113
1114    pub fn remove_field(&mut self, field: &FieldsMessage) {
1115        match field {
1116            FieldsMessage::Pinned => self.pinned = None,
1117        }
1118    }
1119}
1120
1121impl SystemMessage {
1122    pub fn into_message(self, channel: String) -> Message {
1123        Message {
1124            id: Ulid::new().to_string(),
1125            channel,
1126            author: "00000000000000000000000000".to_string(),
1127            system: Some(self),
1128
1129            ..Default::default()
1130        }
1131    }
1132}
1133
1134impl Interactions {
1135    /// Validate interactions info is correct
1136    pub async fn validate(&self, db: &Database, permissions: &PermissionValue) -> Result<()> {
1137        let config = config().await;
1138
1139        if let Some(reactions) = &self.reactions {
1140            permissions.throw_if_lacking_channel_permission(ChannelPermission::React)?;
1141
1142            if reactions.len() > config.features.limits.global.message_reactions {
1143                return Err(create_error!(InvalidOperation));
1144            }
1145
1146            for reaction in reactions {
1147                if !Emoji::can_use(db, reaction).await? {
1148                    return Err(create_error!(InvalidOperation));
1149                }
1150            }
1151        }
1152
1153        Ok(())
1154    }
1155
1156    /// Check if we can use a given emoji to react
1157    pub fn can_use(&self, emoji: &str) -> bool {
1158        if self.restrict_reactions {
1159            if let Some(reactions) = &self.reactions {
1160                reactions.contains(emoji)
1161            } else {
1162                false
1163            }
1164        } else {
1165            true
1166        }
1167    }
1168
1169    /// Check if default initialisation of fields
1170    pub fn is_default(&self) -> bool {
1171        !self.restrict_reactions && self.reactions.is_none()
1172    }
1173}