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 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 } = 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 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(())), MessageAuthor::Webhook(..) => None, } {
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 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 Ok(message) => {
466 if mention && allow_mentions {
467 user_mentions.insert(message.author.to_owned());
468 }
469
470 if !replies.contains(&message.id) {
474 replies.push(message.id);
475 }
476 }
477 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 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)); if !user_mentions.is_empty() {
512 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 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 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 for sendable_embed in data.embeds.unwrap_or_default() {
593 message.attach_sendable_embed(db, sendable_embed).await?;
594 }
595
596 message.content = data.content;
598
599 message.nonce = Some(idempotency.into_key());
601
602 message
604 .send(db, amqp, author, user, member, &channel, generate_embeds)
605 .await?;
606
607 Ok(message)
608 }
609
610 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 mentions_elsewhere: bool,
621 ) -> Result<()> {
622 db.insert_message(self).await?;
623
624 EventV1::Message(self.clone().into_model(user, member))
626 .p(self.channel.to_string())
627 .await;
628
629 #[cfg(feature = "tasks")]
631 tasks::last_message_id::queue(self.channel.to_string(), self.id.to_string(), is_dm).await;
632
633 #[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 #[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 #[allow(clippy::too_many_arguments)]
670 pub async fn send(
671 &mut self,
672 db: &Database,
673 _amqp: Option<&AMQP>, 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 #[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, )],
718 },
719 )
720 .await;
721 }
722
723 Ok(())
724 }
725
726 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 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 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 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 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 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 pub async fn add_reaction(&self, db: &Database, user: &User, emoji: &str) -> Result<()> {
934 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 if !self.interactions.can_use(emoji) {
944 return Err(create_error!(InvalidOperation));
945 }
946
947 if !Emoji::can_use(db, emoji).await? {
949 return Err(create_error!(InvalidOperation));
950 }
951
952 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 db.add_reaction(&self.id, emoji, &user.id).await
964 }
965
966 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 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 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 pub async fn remove_reaction(&self, db: &Database, user: &str, emoji: &str) -> Result<()> {
1034 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 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 db.clear_reaction(&self.id, emoji).await
1058 } else {
1059 db.remove_reaction(&self.id, emoji, user).await
1061 }
1062 }
1063
1064 pub async fn clear_reaction(&self, db: &Database, emoji: &str) -> Result<()> {
1066 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 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 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 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 pub fn is_default(&self) -> bool {
1136 !self.restrict_reactions && self.reactions.is_none()
1137 }
1138}