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 pub struct Message {
30 #[serde(rename = "_id")]
32 pub id: String,
33 #[serde(skip_serializing_if = "Option::is_none")]
35 pub nonce: Option<String>,
36 pub channel: String,
38 pub author: String,
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub webhook: Option<MessageWebhook>,
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub content: Option<String>,
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub system: Option<SystemMessage>,
49 #[serde(skip_serializing_if = "Option::is_none")]
51 pub attachments: Option<Vec<File>>,
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub edited: Option<Timestamp>,
55 #[serde(skip_serializing_if = "Option::is_none")]
57 pub embeds: Option<Vec<Embed>>,
58 #[serde(skip_serializing_if = "Option::is_none")]
60 pub mentions: Option<Vec<String>>,
61 #[serde(skip_serializing_if = "Option::is_none")]
63 pub role_mentions: Option<Vec<String>>,
64 #[serde(skip_serializing_if = "Option::is_none")]
66 pub replies: Option<Vec<String>>,
67 #[serde(skip_serializing_if = "IndexMap::is_empty", default)]
69 pub reactions: IndexMap<String, IndexSet<String>>,
70 #[serde(skip_serializing_if = "Interactions::is_default", default)]
72 pub interactions: Interactions,
73 #[serde(skip_serializing_if = "Option::is_none")]
75 pub masquerade: Option<Masquerade>,
76 #[serde(skip_serializing_if = "crate::if_option_false")]
78 pub pinned: Option<bool>,
79
80 #[serde(skip_serializing_if = "Option::is_none")]
82 pub flags: Option<u32>,
83 },
84 "PartialMessage"
85);
86
87auto_derived!(
88 #[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 pub struct Masquerade {
126 #[serde(skip_serializing_if = "Option::is_none")]
128 pub name: Option<String>,
129 #[serde(skip_serializing_if = "Option::is_none")]
131 pub avatar: Option<String>,
132 #[serde(skip_serializing_if = "Option::is_none")]
136 pub colour: Option<String>,
137 }
138
139 #[derive(Default)]
141 pub struct Interactions {
142 #[serde(skip_serializing_if = "Option::is_none", default)]
144 pub reactions: Option<IndexSet<String>>,
145 #[serde(skip_serializing_if = "crate::if_false", default)]
149 pub restrict_reactions: bool,
150 }
151
152 pub struct AppendMessage {
154 #[serde(skip_serializing_if = "Option::is_none")]
156 pub embeds: Option<Vec<Embed>>,
157 }
158
159 #[serde(untagged)]
163 pub enum MessageTimePeriod {
164 Relative {
165 nearby: String,
171 },
172 Absolute {
173 before: Option<String>,
175 after: Option<String>,
177 sort: Option<MessageSort>,
179 },
180 }
181
182 #[derive(Default)]
184 pub struct MessageFilter {
185 pub channel: Option<String>,
187 pub author: Option<String>,
189 pub query: Option<String>,
191 pub pinned: Option<bool>,
193 }
194
195 pub struct MessageQuery {
197 pub limit: Option<i64>,
201 #[serde(flatten)]
203 pub filter: MessageFilter,
204 #[serde(flatten)]
206 pub time_period: MessageTimePeriod,
207 }
208
209 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 #[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 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 return Err(create_error!(InvalidProperty));
313 }
314
315 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 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 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 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 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 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(())), MessageAuthor::Webhook(..) => None, } {
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 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 Ok(message) => {
467 if mention && allow_mentions {
468 user_mentions.insert(message.author.to_owned());
469 }
470
471 if !replies.contains(&message.id) {
475 replies.push(message.id);
476 }
477 }
478 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 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)); if !user_mentions.is_empty() {
513 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 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 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 for sendable_embed in data.embeds.unwrap_or_default() {
594 message.attach_sendable_embed(db, sendable_embed).await?;
595 }
596
597 message.content = data.content;
599
600 message.nonce = Some(idempotency.into_key());
602
603 message
605 .send(db, amqp, author, user, member, &channel, generate_embeds)
606 .await?;
607
608 Ok(message)
609 }
610
611 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 mentions_elsewhere: bool,
622 ) -> Result<()> {
623 db.insert_message(self).await?;
624
625 EventV1::Message(self.clone().into_model(user, member))
627 .p(self.channel.to_string())
628 .await;
629
630 #[cfg(feature = "tasks")]
632 tasks::last_message_id::queue(self.channel.to_string(), self.id.to_string(), is_dm).await;
633
634 #[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 #[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 #[allow(clippy::too_many_arguments)]
671 pub async fn send(
672 &mut self,
673 db: &Database,
674 _amqp: Option<&AMQP>, 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 #[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, )],
728 },
729 )
730 .await;
731 }
732
733 Ok(())
734 }
735
736 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 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 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 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 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 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 pub async fn add_reaction(&self, db: &Database, user: &User, emoji: &str) -> Result<()> {
944 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 if !self.interactions.can_use(emoji) {
954 return Err(create_error!(InvalidOperation));
955 }
956
957 if !Emoji::can_use(db, emoji).await? {
959 return Err(create_error!(InvalidOperation));
960 }
961
962 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 db.add_reaction(&self.id, emoji, &user.id).await
974 }
975
976 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 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 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 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 pub async fn remove_reaction(&self, db: &Database, user: &str, emoji: &str) -> Result<()> {
1069 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 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 db.clear_reaction(&self.id, emoji).await
1093 } else {
1094 db.remove_reaction(&self.id, emoji, user).await
1096 }
1097 }
1098
1099 pub async fn clear_reaction(&self, db: &Database, emoji: &str) -> Result<()> {
1101 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 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 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 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 pub fn is_default(&self) -> bool {
1171 !self.restrict_reactions && self.reactions.is_none()
1172 }
1173}