1use std::time::SystemTime;
2
3use indexmap::{IndexMap, IndexSet};
4use revolt_config::config;
5
6#[cfg(feature = "validator")]
7use validator::Validate;
8
9#[cfg(feature = "rocket")]
10use rocket::{FromForm, FromFormField};
11
12use iso8601_timestamp::Timestamp;
13
14use super::{Channel, Embed, File, Member, MessageWebhook, User, Webhook, RE_COLOUR};
15
16auto_derived_partial!(
17 pub struct Message {
19 #[serde(rename = "_id")]
21 pub id: String,
22 #[serde(skip_serializing_if = "Option::is_none")]
24 pub nonce: Option<String>,
25 pub channel: String,
27 pub author: String,
29 #[serde(skip_serializing_if = "Option::is_none")]
31 pub user: Option<User>,
32 #[serde(skip_serializing_if = "Option::is_none")]
34 pub member: Option<Member>,
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pub webhook: Option<MessageWebhook>,
38 #[serde(skip_serializing_if = "Option::is_none")]
40 pub content: Option<String>,
41 #[serde(skip_serializing_if = "Option::is_none")]
43 pub system: Option<SystemMessage>,
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub attachments: Option<Vec<File>>,
47 #[serde(skip_serializing_if = "Option::is_none")]
49 pub edited: Option<Timestamp>,
50 #[serde(skip_serializing_if = "Option::is_none")]
52 pub embeds: Option<Vec<Embed>>,
53 #[serde(skip_serializing_if = "Option::is_none")]
55 pub mentions: Option<Vec<String>>,
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub role_mentions: Option<Vec<String>>,
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub replies: Option<Vec<String>>,
62 #[serde(skip_serializing_if = "IndexMap::is_empty", default)]
64 pub reactions: IndexMap<String, IndexSet<String>>,
65 #[serde(skip_serializing_if = "Interactions::is_default", default)]
67 pub interactions: Interactions,
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub masquerade: Option<Masquerade>,
71 #[serde(skip_serializing_if = "crate::if_option_false")]
73 pub pinned: Option<bool>,
74
75 #[cfg_attr(
79 feature = "serde",
80 serde(skip_serializing_if = "crate::if_zero_u32", default)
81 )]
82 pub flags: u32,
83 },
84 "PartialMessage"
85);
86
87auto_derived!(
88 #[serde(untagged)]
90 pub enum BulkMessageResponse {
91 JustMessages(
92 Vec<Message>,
94 ),
95 MessagesAndUsers {
96 messages: Vec<Message>,
98 users: Vec<User>,
100 #[serde(skip_serializing_if = "Option::is_none")]
102 members: Option<Vec<Member>>,
103 },
104 }
105
106 #[serde(tag = "type")]
108 pub enum SystemMessage {
109 #[serde(rename = "text")]
110 Text { content: String },
111 #[serde(rename = "user_added")]
112 UserAdded { id: String, by: String },
113 #[serde(rename = "user_remove")]
114 UserRemove { id: String, by: String },
115 #[serde(rename = "user_joined")]
116 UserJoined { id: String },
117 #[serde(rename = "user_left")]
118 UserLeft { id: String },
119 #[serde(rename = "user_kicked")]
120 UserKicked { id: String },
121 #[serde(rename = "user_banned")]
122 UserBanned { id: String },
123 #[serde(rename = "channel_renamed")]
124 ChannelRenamed { name: String, by: String },
125 #[serde(rename = "channel_description_changed")]
126 ChannelDescriptionChanged { by: String },
127 #[serde(rename = "channel_icon_changed")]
128 ChannelIconChanged { by: String },
129 #[serde(rename = "channel_ownership_changed")]
130 ChannelOwnershipChanged { from: String, to: String },
131 #[serde(rename = "message_pinned")]
132 MessagePinned { id: String, by: String },
133 #[serde(rename = "message_unpinned")]
134 MessageUnpinned { id: String, by: String },
135 #[serde(rename = "call_started")]
136 CallStarted {
137 by: String,
138 finished_at: Option<Timestamp>,
139 },
140 }
141
142 #[cfg_attr(feature = "validator", derive(Validate))]
144 pub struct Masquerade {
145 #[serde(skip_serializing_if = "Option::is_none")]
147 #[validate(length(min = 1, max = 32))]
148 pub name: Option<String>,
149 #[serde(skip_serializing_if = "Option::is_none")]
151 #[validate(length(min = 1, max = 256))]
152 pub avatar: Option<String>,
153 #[serde(skip_serializing_if = "Option::is_none")]
157 #[validate(length(min = 1, max = 128), regex = "RE_COLOUR")]
158 pub colour: Option<String>,
159 }
160
161 #[derive(Default)]
163 pub struct Interactions {
164 #[serde(skip_serializing_if = "Option::is_none", default)]
166 pub reactions: Option<IndexSet<String>>,
167 #[serde(skip_serializing_if = "crate::if_false", default)]
171 pub restrict_reactions: bool,
172 }
173
174 pub struct AppendMessage {
176 #[serde(skip_serializing_if = "Option::is_none")]
178 pub embeds: Option<Vec<Embed>>,
179 }
180
181 #[derive(Default)]
185 #[cfg_attr(feature = "rocket", derive(FromFormField))]
186 pub enum MessageSort {
187 #[default]
189 Relevance,
190 Latest,
192 Oldest,
194 }
195
196 pub struct PushNotification {
198 pub author: String,
200 pub icon: String,
202 #[serde(skip_serializing_if = "Option::is_none")]
204 pub image: Option<String>,
205 pub body: String,
207 #[serde(skip_serializing_if = "Option::is_none")]
209 pub raw_body: Option<String>,
210 pub tag: String,
212 pub timestamp: u64,
214 pub url: String,
216 pub message: Message,
218 pub channel: Channel,
220 }
221
222 #[derive(Default)]
224 #[cfg_attr(feature = "validator", derive(Validate))]
225 pub struct SendableEmbed {
226 #[cfg_attr(feature = "validator", validate(length(min = 1, max = 256)))]
227 pub icon_url: Option<String>,
228 #[cfg_attr(feature = "validator", validate(length(min = 1, max = 256)))]
229 pub url: Option<String>,
230 #[cfg_attr(feature = "validator", validate(length(min = 1, max = 100)))]
231 pub title: Option<String>,
232 #[cfg_attr(feature = "validator", validate(length(min = 1, max = 2000)))]
233 pub description: Option<String>,
234 pub media: Option<String>,
235 #[cfg_attr(
236 feature = "validator",
237 validate(length(min = 1, max = 128), regex = "RE_COLOUR")
238 )]
239 pub colour: Option<String>,
240 }
241
242 pub struct ReplyIntent {
244 pub id: String,
246 pub mention: bool,
248 pub fail_if_not_exists: Option<bool>,
252 }
253
254 #[cfg_attr(feature = "validator", derive(Validate))]
256 pub struct DataMessageSend {
257 #[cfg_attr(feature = "validator", validate(length(min = 1, max = 64)))]
261 pub nonce: Option<String>,
262
263 #[cfg_attr(feature = "validator", validate(length(min = 0)))]
265 pub content: Option<String>,
266 pub attachments: Option<Vec<String>>,
268 pub replies: Option<Vec<ReplyIntent>>,
270 #[cfg_attr(feature = "validator", validate)]
274 pub embeds: Option<Vec<SendableEmbed>>,
275 #[cfg_attr(feature = "validator", validate)]
277 pub masquerade: Option<Masquerade>,
278 pub interactions: Option<Interactions>,
280
281 pub flags: Option<u32>,
285 }
286
287 #[cfg_attr(feature = "validator", derive(Validate))]
289 #[cfg_attr(feature = "rocket", derive(FromForm))]
290 pub struct OptionsQueryMessages {
291 #[cfg_attr(feature = "validator", validate(range(min = 1, max = 100)))]
295 pub limit: Option<i64>,
296 #[cfg_attr(feature = "validator", validate(length(min = 26, max = 26)))]
298 pub before: Option<String>,
299 #[cfg_attr(feature = "validator", validate(length(min = 26, max = 26)))]
301 pub after: Option<String>,
302 pub sort: Option<MessageSort>,
304 #[cfg_attr(feature = "validator", validate(length(min = 26, max = 26)))]
310 pub nearby: Option<String>,
311 pub include_users: Option<bool>,
313 }
314
315 #[cfg_attr(feature = "validator", derive(Validate))]
317 pub struct DataMessageSearch {
318 #[cfg_attr(feature = "validator", validate(length(min = 1, max = 64)))]
322 pub query: Option<String>,
323 pub pinned: Option<bool>,
325
326 #[cfg_attr(feature = "validator", validate(range(min = 1, max = 100)))]
328 pub limit: Option<i64>,
329 #[cfg_attr(feature = "validator", validate(length(min = 26, max = 26)))]
331 pub before: Option<String>,
332 #[cfg_attr(feature = "validator", validate(length(min = 26, max = 26)))]
334 pub after: Option<String>,
335 #[cfg_attr(feature = "serde", serde(default = "MessageSort::default"))]
339 pub sort: MessageSort,
340 pub include_users: Option<bool>,
342 }
343
344 #[cfg_attr(feature = "validator", derive(Validate))]
346 pub struct DataEditMessage {
347 #[cfg_attr(feature = "validator", validate(length(min = 1)))]
349 pub content: Option<String>,
350 #[cfg_attr(feature = "validator", validate(length(min = 0, max = 10)))]
352 pub embeds: Option<Vec<SendableEmbed>>,
353 }
354
355 #[cfg_attr(
357 feature = "validator",
358 cfg_attr(feature = "validator", derive(Validate))
359 )]
360 pub struct OptionsBulkDelete {
361 #[validate(length(min = 1, max = 100))]
363 pub ids: Vec<String>,
364 }
365
366 #[cfg_attr(feature = "rocket", derive(FromForm))]
368 pub struct OptionsUnreact {
369 pub user_id: Option<String>,
371 pub remove_all: Option<bool>,
373 }
374
375 #[repr(u32)]
377 pub enum MessageFlags {
378 SuppressNotifications = 1,
380 MentionsEveryone = 2,
382 MentionsOnline = 3,
385 }
386
387 pub enum FieldsMessage {
389 Pinned,
390 }
391);
392
393#[derive(Clone)]
395pub enum MessageAuthor<'a> {
396 User(&'a User),
397 Webhook(&'a Webhook),
398 System {
399 username: &'a str,
400 avatar: Option<&'a str>,
401 },
402}
403
404impl Interactions {
405 pub fn is_default(&self) -> bool {
407 !self.restrict_reactions && self.reactions.is_none()
408 }
409}
410
411impl MessageAuthor<'_> {
412 pub fn id(&self) -> &str {
413 match self {
414 MessageAuthor::User(user) => &user.id,
415 MessageAuthor::Webhook(webhook) => &webhook.id,
416 MessageAuthor::System { .. } => "00000000000000000000000000",
417 }
418 }
419
420 pub fn avatar(&self) -> Option<&str> {
421 match self {
422 MessageAuthor::User(user) => user.avatar.as_ref().map(|file| file.id.as_str()),
423 MessageAuthor::Webhook(webhook) => webhook.avatar.as_ref().map(|file| file.id.as_str()),
424 MessageAuthor::System { avatar, .. } => *avatar,
425 }
426 }
427
428 pub fn username(&self) -> &str {
429 match self {
430 MessageAuthor::User(user) => &user.username,
431 MessageAuthor::Webhook(webhook) => &webhook.name,
432 MessageAuthor::System { username, .. } => username,
433 }
434 }
435}
436
437impl From<SystemMessage> for String {
438 fn from(s: SystemMessage) -> String {
439 match s {
440 SystemMessage::Text { content } => content,
441 SystemMessage::UserAdded { .. } => "User added to the channel.".to_string(),
442 SystemMessage::UserRemove { .. } => "User removed from the channel.".to_string(),
443 SystemMessage::UserJoined { .. } => "User joined the channel.".to_string(),
444 SystemMessage::UserLeft { .. } => "User left the channel.".to_string(),
445 SystemMessage::UserKicked { .. } => "User kicked from the channel.".to_string(),
446 SystemMessage::UserBanned { .. } => "User banned from the channel.".to_string(),
447 SystemMessage::ChannelRenamed { .. } => "Channel renamed.".to_string(),
448 SystemMessage::ChannelDescriptionChanged { .. } => {
449 "Channel description changed.".to_string()
450 }
451 SystemMessage::ChannelIconChanged { .. } => "Channel icon changed.".to_string(),
452 SystemMessage::ChannelOwnershipChanged { .. } => {
453 "Channel ownership changed.".to_string()
454 }
455 SystemMessage::MessagePinned { .. } => "Message pinned.".to_string(),
456 SystemMessage::MessageUnpinned { .. } => "Message unpinned.".to_string(),
457 SystemMessage::CallStarted { .. } => "Call started.".to_string(),
458 }
459 }
460}
461
462impl PushNotification {
463 pub async fn from(msg: Message, author: Option<MessageAuthor<'_>>, channel: Channel) -> Self {
465 let config = config().await;
466
467 let icon = if let Some(author) = &author {
468 if let Some(avatar) = author.avatar() {
469 format!("{}/avatars/{}", config.hosts.autumn, avatar)
470 } else {
471 format!("{}/users/{}/default_avatar", config.hosts.api, author.id())
472 }
473 } else {
474 format!("{}/assets/logo.png", config.hosts.app)
475 };
476
477 let image = msg.attachments.as_ref().and_then(|attachments| {
478 attachments
479 .first()
480 .map(|v| format!("{}/attachments/{}", config.hosts.autumn, v.id))
481 });
482
483 let body = if let Some(ref sys) = msg.system {
484 sys.clone().into()
485 } else if let Some(ref text) = msg.content {
486 text.clone()
487 } else if let Some(text) = msg.embeds.as_ref().and_then(|embeds| match embeds.first() {
488 Some(Embed::Image(_)) => Some("Sent an image".to_string()),
489 Some(Embed::Video(_)) => Some("Sent a video".to_string()),
490 Some(Embed::Text(e)) => e
491 .description
492 .clone()
493 .or(e.title.clone().or(Some("Empty Embed".to_string()))),
494 Some(Embed::Website(e)) => e.title.clone().or(e
495 .description
496 .clone()
497 .or(e.site_name.clone().or(Some("Empty Embed".to_string())))),
498 Some(Embed::None) => Some("Empty Message".to_string()), None => Some("Empty Message".to_string()), }) {
501 text
502 } else {
503 "Empty Message".to_string()
504 };
505
506 let timestamp = SystemTime::now()
507 .duration_since(SystemTime::UNIX_EPOCH)
508 .expect("Time went backwards")
509 .as_secs();
510
511 Self {
512 author: author
513 .map(|x| x.username().to_string())
514 .unwrap_or_else(|| "Revolt".to_string()),
515 icon,
516 image,
517 body,
518 raw_body: None,
519 tag: channel.id().to_string(),
520 timestamp,
521 url: format!("{}/channel/{}/{}", config.hosts.app, channel.id(), msg.id),
522 message: msg,
523 channel,
524 }
525 }
526}