revolt_database/models/messages/
model.rs

1use std::{collections::HashSet, hash::RandomState};
2
3use indexmap::{IndexMap, IndexSet};
4use iso8601_timestamp::Timestamp;
5use revolt_config::{config, FeaturesLimits};
6use revolt_models::v0::{
7    self, BulkMessageResponse, DataMessageSend, Embed, MessageAuthor, MessageFlags, MessageSort,
8    MessageWebhook, PushNotification, ReplyIntent, SendableEmbed, Text,
9};
10use revolt_permissions::{calculate_channel_permissions, ChannelPermission, PermissionValue};
11use revolt_result::{ErrorType, Result};
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        } = message_mentions;
392
393        if allow_mass_mentions && server_id.is_some() && !role_mentions.is_empty() {
394            let server_data = db
395                .fetch_server(server_id.unwrap().as_str())
396                .await
397                .expect("Failed to fetch server");
398
399            role_mentions.retain(|role_id| server_data.roles.contains_key(role_id));
400        }
401
402        // Validate the user can perform a mass mention
403        if !config.features.mass_mentions_enabled
404            && (mentions_everyone || mentions_online || !role_mentions.is_empty())
405        {
406            mentions_everyone = false;
407            mentions_online = false;
408            role_mentions.clear();
409        } else if mentions_everyone || mentions_online || !role_mentions.is_empty() {
410            debug!(
411                "Mentioned everyone: {}, mentioned online: {}, mentioned roles: {:?}",
412                mentions_everyone, mentions_online, &role_mentions
413            );
414            if let Some(user) = match author {
415                MessageAuthor::User(user) => Some(Ok(user)),
416                MessageAuthor::System { .. } => Some(Err(())), // DISALLOWED
417                MessageAuthor::Webhook(..) => None,            // Bypass check
418            } {
419                if user.is_err() {
420                    return Err(create_error!(InvalidProperty));
421                }
422                let owned_user: User = user.unwrap().to_owned().into();
423
424                let mut query = DatabasePermissionQuery::new(db, &owned_user).channel(&channel);
425                let perms = calculate_channel_permissions(&mut query).await;
426
427                if (mentions_everyone || mentions_online)
428                    && !perms.has_channel_permission(ChannelPermission::MentionEveryone)
429                {
430                    return Err(create_error!(MissingPermission {
431                        permission: ChannelPermission::MentionEveryone.to_string()
432                    }));
433                }
434
435                if !role_mentions.is_empty()
436                    && !perms.has_channel_permission(ChannelPermission::MentionRoles)
437                {
438                    return Err(create_error!(MissingPermission {
439                        permission: ChannelPermission::MentionRoles.to_string()
440                    }));
441                }
442            }
443        }
444
445        // Verify replies are valid.
446        let mut replies = Vec::new();
447
448        if let Some(entries) = data.replies {
449            if entries.len() > config.features.limits.global.message_replies {
450                return Err(create_error!(TooManyReplies {
451                    max: config.features.limits.global.message_replies,
452                }));
453            }
454
455            replies.reserve(entries.len());
456
457            for ReplyIntent {
458                id,
459                mention,
460                fail_if_not_exists,
461            } in entries
462            {
463                match db.fetch_message(&id).await {
464                    // Referenced message exists
465                    Ok(message) => {
466                        if mention && allow_mentions {
467                            user_mentions.insert(message.author.to_owned());
468                        }
469
470                        // This is O(n^2), but this is faster than a HashSet
471                        // when n < 20; as long as the message_replies limit
472                        // is reasonable, this will be fast.
473                        if !replies.contains(&message.id) {
474                            replies.push(message.id);
475                        }
476                    }
477                    // If the referenced message doesn't exist and fail_if_not_exists
478                    // is set to false, send the message without the reply.
479                    Err(e) => {
480                        if !matches!(e.error_type, ErrorType::NotFound)
481                            || fail_if_not_exists.unwrap_or(true)
482                        {
483                            return Err(e);
484                        }
485                    }
486                }
487            }
488        }
489
490        // Validate the mentions go to users in the channel/server
491        if !user_mentions.is_empty() {
492            #[allow(deprecated)]
493            match channel {
494                Channel::DirectMessage { ref recipients, .. }
495                | Channel::Group { ref recipients, .. } => {
496                    let recipients_hash = HashSet::<&String, RandomState>::from_iter(recipients);
497                    user_mentions.retain(|m| recipients_hash.contains(m));
498                    role_mentions.clear();
499                }
500                Channel::TextChannel { ref server, .. }=> {
501                    let mentions_vec = Vec::from_iter(user_mentions.iter().cloned());
502
503                    let valid_members = db.fetch_members(server.as_str(), &mentions_vec[..]).await;
504                    if let Ok(valid_members) = valid_members {
505                        let valid_mentions = HashSet::<&String, RandomState>::from_iter(
506                            valid_members.iter().map(|m| &m.id.user),
507                        );
508
509                        user_mentions.retain(|m| valid_mentions.contains(m)); // quick pass, validate mentions are in the server
510
511                        if !user_mentions.is_empty() {
512                            // if there are still mentions, drill down to a channel-level
513                            let member_channel_view_perms =
514                                BulkDatabasePermissionQuery::from_server_id(db, server)
515                                    .await
516                                    .channel(&channel)
517                                    .members(&valid_members)
518                                    .members_can_see_channel()
519                                    .await;
520
521                            user_mentions
522                                .retain(|m| *member_channel_view_perms.get(m).unwrap_or(&false));
523                        }
524                    } else {
525                        revolt_config::capture_error(&valid_members.unwrap_err());
526                        return Err(create_error!(InternalError));
527                    }
528                }
529                Channel::SavedMessages { .. } => {
530                    user_mentions.clear();
531                }
532            }
533        }
534
535        if !user_mentions.is_empty() {
536            message
537                .mentions
538                .replace(user_mentions.into_iter().collect());
539        }
540
541        if !role_mentions.is_empty() {
542            message
543                .role_mentions
544                .replace(role_mentions.into_iter().collect());
545        }
546
547        if !replies.is_empty() {
548            message.replies.replace(replies);
549        }
550
551        // Calculate final message flags
552        let mut flag_value = MessageFlagsValue(0);
553        flag_value
554            .set(MessageFlags::SuppressNotifications, suppress_notifications)
555            .set(MessageFlags::MentionsEveryone, mentions_everyone)
556            .set(MessageFlags::MentionsOnline, mentions_online);
557
558        message.flags = Some(flag_value.0);
559
560        // Add attachments to message.
561        let mut attachments = vec![];
562        if data
563            .attachments
564            .as_ref()
565            .is_some_and(|v| v.len() > limits.message_attachments)
566        {
567            return Err(create_error!(TooManyAttachments {
568                max: limits.message_attachments,
569            }));
570        }
571
572        if data
573            .embeds
574            .as_ref()
575            .is_some_and(|v| v.len() > config.features.limits.global.message_embeds)
576        {
577            return Err(create_error!(TooManyEmbeds {
578                max: config.features.limits.global.message_embeds,
579            }));
580        }
581
582        for attachment_id in data.attachments.as_deref().unwrap_or_default() {
583            attachments
584                .push(File::use_attachment(db, attachment_id, &message_id, author.id()).await?);
585        }
586
587        if !attachments.is_empty() {
588            message.attachments.replace(attachments);
589        }
590
591        // Process included embeds.
592        for sendable_embed in data.embeds.unwrap_or_default() {
593            message.attach_sendable_embed(db, sendable_embed).await?;
594        }
595
596        // Set content
597        message.content = data.content;
598
599        // Pass-through nonce value for clients
600        message.nonce = Some(idempotency.into_key());
601
602        // Send the message
603        message
604            .send(db, amqp, author, user, member, &channel, generate_embeds)
605            .await?;
606
607        Ok(message)
608    }
609
610    /// Send a message without any notifications
611    pub async fn send_without_notifications(
612        &mut self,
613        db: &Database,
614        user: Option<v0::User>,
615        member: Option<v0::Member>,
616        is_dm: bool,
617        generate_embeds: bool,
618        // This determines if this function should queue the mentions task or if somewhere else will.
619        // If this is true, you MUST call tasks::ack::queue yourself.
620        mentions_elsewhere: bool,
621    ) -> Result<()> {
622        db.insert_message(self).await?;
623
624        // Fan out events
625        EventV1::Message(self.clone().into_model(user, member))
626            .p(self.channel.to_string())
627            .await;
628
629        // Update last_message_id
630        #[cfg(feature = "tasks")]
631        tasks::last_message_id::queue(self.channel.to_string(), self.id.to_string(), is_dm).await;
632
633        // Add mentions for affected users
634        #[cfg(feature = "tasks")]
635        if !mentions_elsewhere {
636            if let Some(mentions) = &self.mentions {
637                tasks::ack::queue_message(
638                    self.channel.to_string(),
639                    AckEvent::ProcessMessage {
640                        messages: vec![(
641                            None,
642                            self.clone(),
643                            mentions.clone(),
644                            self.has_suppressed_notifications(),
645                        )],
646                    },
647                )
648                .await;
649            }
650        }
651
652        // Generate embeds
653        #[cfg(feature = "tasks")]
654        if generate_embeds {
655            if let Some(content) = &self.content {
656                tasks::process_embeds::queue(
657                    self.channel.to_string(),
658                    self.id.to_string(),
659                    content.clone(),
660                )
661                .await;
662            }
663        }
664
665        Ok(())
666    }
667
668    /// Send a message
669    #[allow(clippy::too_many_arguments)]
670    pub async fn send(
671        &mut self,
672        db: &Database,
673        _amqp: Option<&AMQP>, // this is optional mostly for tests.
674        author: MessageAuthor<'_>,
675        user: Option<v0::User>,
676        member: Option<v0::Member>,
677        channel: &Channel,
678        generate_embeds: bool,
679    ) -> Result<()> {
680        self.send_without_notifications(
681            db,
682            user.clone(),
683            member.clone(),
684            matches!(channel, Channel::DirectMessage { .. }),
685            generate_embeds,
686            true,
687        )
688        .await?;
689
690        if !self.has_suppressed_notifications()
691            && (self.mentions.is_some() || self.contains_mass_push_mention())
692        {
693            // send Push notifications
694            #[cfg(feature = "tasks")]
695            tasks::ack::queue_message(
696                self.channel.to_string(),
697                AckEvent::ProcessMessage {
698                    messages: vec![(
699                        Some(
700                            PushNotification::from(
701                                self.clone().into_model(user, member),
702                                Some(author),
703                                channel.to_owned().into(),
704                            )
705                            .await,
706                        ),
707                        self.clone(),
708                        match channel {
709                            Channel::DirectMessage { recipients, .. }
710                            | Channel::Group { recipients, .. } => recipients.clone(),
711                            Channel::TextChannel { .. } => {
712                                self.mentions.clone().unwrap_or_default()
713                            }
714                            _ => vec![],
715                        },
716                        false, // branch already dictates this
717                    )],
718                },
719            )
720            .await;
721        }
722
723        Ok(())
724    }
725
726    /// Create text embed from sendable embed
727    pub async fn create_embed(&self, db: &Database, embed: SendableEmbed) -> Result<Embed> {
728        embed.validate().map_err(|error| {
729            create_error!(FailedValidation {
730                error: error.to_string()
731            })
732        })?;
733
734        let media = if let Some(id) = embed.media {
735            Some(File::use_attachment(db, &id, &self.id, &self.author).await?)
736        } else {
737            None
738        };
739
740        Ok(Embed::Text(Text {
741            icon_url: embed.icon_url,
742            url: embed.url,
743            title: embed.title,
744            description: embed.description,
745            media: media.map(|m| m.into()),
746            colour: embed.colour,
747        }))
748    }
749
750    /// Whether this message has suppressed notifications
751    pub fn has_suppressed_notifications(&self) -> bool {
752        if let Some(flags) = self.flags {
753            flags & MessageFlags::SuppressNotifications as u32
754                == MessageFlags::SuppressNotifications as u32
755        } else {
756            false
757        }
758    }
759
760    pub fn contains_mass_push_mention(&self) -> bool {
761        let ping = if let Some(flags) = self.flags {
762            let flags = MessageFlagsValue(flags);
763            flags.has(MessageFlags::MentionsEveryone)
764        } else {
765            false
766        };
767
768        ping || self.role_mentions.is_some()
769    }
770
771    /// Update message data
772    pub async fn update(
773        &mut self,
774        db: &Database,
775        partial: PartialMessage,
776        remove: Vec<FieldsMessage>,
777    ) -> Result<()> {
778        self.apply_options(partial.clone());
779
780        for field in &remove {
781            self.remove_field(field);
782        }
783
784        db.update_message(&self.id, &partial, remove.clone())
785            .await?;
786
787        EventV1::MessageUpdate {
788            id: self.id.clone(),
789            channel: self.channel.clone(),
790            data: partial.into(),
791            clear: remove.into_iter().map(|field| field.into()).collect(),
792        }
793        .p(self.channel.clone())
794        .await;
795
796        Ok(())
797    }
798
799    /// Helper function to fetch many messages with users
800    pub async fn fetch_with_users(
801        db: &Database,
802        query: MessageQuery,
803        perspective: &User,
804        include_users: Option<bool>,
805        server_id: Option<&str>,
806    ) -> Result<BulkMessageResponse> {
807        let messages: Vec<v0::Message> = db
808            .fetch_messages(query)
809            .await?
810            .into_iter()
811            .map(|msg| msg.into_model(None, None))
812            .collect();
813
814        if let Some(true) = include_users {
815            let user_ids = messages
816                .iter()
817                .flat_map(|m| {
818                    let mut users = vec![m.author.clone()];
819                    if let Some(system) = &m.system {
820                        match system {
821                            v0::SystemMessage::ChannelDescriptionChanged { by } => {
822                                users.push(by.clone())
823                            }
824                            v0::SystemMessage::ChannelIconChanged { by } => users.push(by.clone()),
825                            v0::SystemMessage::ChannelOwnershipChanged { from, to, .. } => {
826                                users.push(from.clone());
827                                users.push(to.clone())
828                            }
829                            v0::SystemMessage::ChannelRenamed { by, .. } => users.push(by.clone()),
830                            v0::SystemMessage::UserAdded { by, id, .. }
831                            | v0::SystemMessage::UserRemove { by, id, .. } => {
832                                users.push(by.clone());
833                                users.push(id.clone());
834                            }
835                            v0::SystemMessage::UserBanned { id, .. }
836                            | v0::SystemMessage::UserKicked { id, .. }
837                            | v0::SystemMessage::UserJoined { id, .. }
838                            | v0::SystemMessage::UserLeft { id, .. } => {
839                                users.push(id.clone());
840                            }
841                            v0::SystemMessage::Text { .. } => {}
842                            v0::SystemMessage::MessagePinned { by, .. } => {
843                                users.push(by.clone());
844                            }
845                            v0::SystemMessage::MessageUnpinned { by, .. } => {
846                                users.push(by.clone());
847                            }
848                            v0::SystemMessage::CallStarted { by, .. } => users.push(by.clone()),
849                        }
850                    }
851                    users
852                })
853                .collect::<HashSet<String>>()
854                .into_iter()
855                .collect::<Vec<String>>();
856            let users = User::fetch_many_ids_as_mutuals(db, perspective, &user_ids).await?;
857
858            Ok(BulkMessageResponse::MessagesAndUsers {
859                messages,
860                users,
861                members: if let Some(server_id) = server_id {
862                    Some(
863                        db.fetch_members(server_id, &user_ids)
864                            .await?
865                            .into_iter()
866                            .map(Into::into)
867                            .collect(),
868                    )
869                } else {
870                    None
871                },
872            })
873        } else {
874            Ok(BulkMessageResponse::JustMessages(messages))
875        }
876    }
877
878    /// Append content to message
879    pub async fn append(
880        db: &Database,
881        id: String,
882        channel: String,
883        append: AppendMessage,
884    ) -> Result<()> {
885        db.append_message(&id, &append).await?;
886
887        EventV1::MessageAppend {
888            id,
889            channel: channel.to_string(),
890            append: append.into(),
891        }
892        .p(channel)
893        .await;
894
895        Ok(())
896    }
897
898    /// Convert sendable embed to text embed and attach to message
899    pub async fn attach_sendable_embed(
900        &mut self,
901        db: &Database,
902        embed: v0::SendableEmbed,
903    ) -> Result<()> {
904        let media: Option<v0::File> = if let Some(id) = embed.media {
905            Some(
906                File::use_attachment(db, &id, &self.id, &self.author)
907                    .await?
908                    .into(),
909            )
910        } else {
911            None
912        };
913
914        let embed = v0::Embed::Text(v0::Text {
915            icon_url: embed.icon_url,
916            url: embed.url,
917            title: embed.title,
918            description: embed.description,
919            media,
920            colour: embed.colour,
921        });
922
923        if let Some(embeds) = &mut self.embeds {
924            embeds.push(embed);
925        } else {
926            self.embeds = Some(vec![embed]);
927        }
928
929        Ok(())
930    }
931
932    /// Add a reaction to a message
933    pub async fn add_reaction(&self, db: &Database, user: &User, emoji: &str) -> Result<()> {
934        // Check how many reactions are already on the message
935        let config = config().await;
936        if self.reactions.len() >= config.features.limits.global.message_reactions
937            && !self.reactions.contains_key(emoji)
938        {
939            return Err(create_error!(InvalidOperation));
940        }
941
942        // Check if the emoji is whitelisted
943        if !self.interactions.can_use(emoji) {
944            return Err(create_error!(InvalidOperation));
945        }
946
947        // Check if the emoji is usable by us
948        if !Emoji::can_use(db, emoji).await? {
949            return Err(create_error!(InvalidOperation));
950        }
951
952        // Send reaction event
953        EventV1::MessageReact {
954            id: self.id.to_string(),
955            channel_id: self.channel.to_string(),
956            user_id: user.id.to_string(),
957            emoji_id: emoji.to_string(),
958        }
959        .p(self.channel.to_string())
960        .await;
961
962        // Add emoji
963        db.add_reaction(&self.id, emoji, &user.id).await
964    }
965
966    /// Validate the sum of content of a message is under threshold
967    pub fn validate_sum(
968        content: &Option<String>,
969        embeds: &[SendableEmbed],
970        max_length: usize,
971    ) -> Result<()> {
972        let mut running_total = 0;
973        if let Some(content) = content {
974            running_total += content.len();
975        }
976
977        for embed in embeds {
978            if let Some(desc) = &embed.description {
979                running_total += desc.len();
980            }
981        }
982
983        if running_total <= max_length {
984            Ok(())
985        } else {
986            Err(create_error!(PayloadTooLarge))
987        }
988    }
989
990    /// Delete a message
991    pub async fn delete(self, db: &Database) -> Result<()> {
992        let file_ids: Vec<String> = self
993            .attachments
994            .map(|files| files.iter().map(|file| file.id.to_string()).collect())
995            .unwrap_or_default();
996
997        if !file_ids.is_empty() {
998            db.mark_attachments_as_deleted(&file_ids).await?;
999        }
1000
1001        db.delete_message(&self.id).await?;
1002
1003        EventV1::MessageDelete {
1004            id: self.id,
1005            channel: self.channel.clone(),
1006        }
1007        .p(self.channel)
1008        .await;
1009        Ok(())
1010    }
1011
1012    /// Bulk delete messages
1013    pub async fn bulk_delete(db: &Database, channel: &str, ids: Vec<String>) -> Result<()> {
1014        let valid_ids = db
1015            .fetch_messages_by_id(&ids)
1016            .await?
1017            .into_iter()
1018            .filter(|msg| msg.channel == channel)
1019            .map(|msg| msg.id)
1020            .collect::<Vec<String>>();
1021
1022        db.delete_messages(channel, &valid_ids).await?;
1023        EventV1::BulkMessageDelete {
1024            channel: channel.to_string(),
1025            ids: valid_ids,
1026        }
1027        .p(channel.to_string())
1028        .await;
1029        Ok(())
1030    }
1031
1032    /// Remove a reaction from a message
1033    pub async fn remove_reaction(&self, db: &Database, user: &str, emoji: &str) -> Result<()> {
1034        // Check if it actually exists
1035        let empty = if let Some(users) = self.reactions.get(emoji) {
1036            if !users.contains(user) {
1037                return Err(create_error!(NotFound));
1038            }
1039
1040            users.len() == 1
1041        } else {
1042            return Err(create_error!(NotFound));
1043        };
1044
1045        // Send reaction event
1046        EventV1::MessageUnreact {
1047            id: self.id.to_string(),
1048            channel_id: self.channel.to_string(),
1049            user_id: user.to_string(),
1050            emoji_id: emoji.to_string(),
1051        }
1052        .p(self.channel.to_string())
1053        .await;
1054
1055        if empty {
1056            // If empty, remove the reaction entirely
1057            db.clear_reaction(&self.id, emoji).await
1058        } else {
1059            // Otherwise only remove that one reaction
1060            db.remove_reaction(&self.id, emoji, user).await
1061        }
1062    }
1063
1064    /// Remove a reaction from a message
1065    pub async fn clear_reaction(&self, db: &Database, emoji: &str) -> Result<()> {
1066        // Send reaction event
1067        EventV1::MessageRemoveReaction {
1068            id: self.id.to_string(),
1069            channel_id: self.channel.to_string(),
1070            emoji_id: emoji.to_string(),
1071        }
1072        .p(self.channel.to_string())
1073        .await;
1074
1075        // Write to database
1076        db.clear_reaction(&self.id, emoji).await
1077    }
1078
1079    pub fn remove_field(&mut self, field: &FieldsMessage) {
1080        match field {
1081            FieldsMessage::Pinned => self.pinned = None,
1082        }
1083    }
1084}
1085
1086impl SystemMessage {
1087    pub fn into_message(self, channel: String) -> Message {
1088        Message {
1089            id: Ulid::new().to_string(),
1090            channel,
1091            author: "00000000000000000000000000".to_string(),
1092            system: Some(self),
1093
1094            ..Default::default()
1095        }
1096    }
1097}
1098
1099impl Interactions {
1100    /// Validate interactions info is correct
1101    pub async fn validate(&self, db: &Database, permissions: &PermissionValue) -> Result<()> {
1102        let config = config().await;
1103
1104        if let Some(reactions) = &self.reactions {
1105            permissions.throw_if_lacking_channel_permission(ChannelPermission::React)?;
1106
1107            if reactions.len() > config.features.limits.global.message_reactions {
1108                return Err(create_error!(InvalidOperation));
1109            }
1110
1111            for reaction in reactions {
1112                if !Emoji::can_use(db, reaction).await? {
1113                    return Err(create_error!(InvalidOperation));
1114                }
1115            }
1116        }
1117
1118        Ok(())
1119    }
1120
1121    /// Check if we can use a given emoji to react
1122    pub fn can_use(&self, emoji: &str) -> bool {
1123        if self.restrict_reactions {
1124            if let Some(reactions) = &self.reactions {
1125                reactions.contains(emoji)
1126            } else {
1127                false
1128            }
1129        } else {
1130            true
1131        }
1132    }
1133
1134    /// Check if default initialisation of fields
1135    pub fn is_default(&self) -> bool {
1136        !self.restrict_reactions && self.reactions.is_none()
1137    }
1138}