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 { by: String, finished_at: Option<Timestamp> },
137 }
138
139 #[cfg_attr(feature = "validator", derive(Validate))]
141 pub struct Masquerade {
142 #[serde(skip_serializing_if = "Option::is_none")]
144 #[validate(length(min = 1, max = 32))]
145 pub name: Option<String>,
146 #[serde(skip_serializing_if = "Option::is_none")]
148 #[validate(length(min = 1, max = 256))]
149 pub avatar: Option<String>,
150 #[serde(skip_serializing_if = "Option::is_none")]
154 #[validate(length(min = 1, max = 128), regex = "RE_COLOUR")]
155 pub colour: Option<String>,
156 }
157
158 #[derive(Default)]
160 pub struct Interactions {
161 #[serde(skip_serializing_if = "Option::is_none", default)]
163 pub reactions: Option<IndexSet<String>>,
164 #[serde(skip_serializing_if = "crate::if_false", default)]
168 pub restrict_reactions: bool,
169 }
170
171 pub struct AppendMessage {
173 #[serde(skip_serializing_if = "Option::is_none")]
175 pub embeds: Option<Vec<Embed>>,
176 }
177
178 #[derive(Default)]
182 #[cfg_attr(feature = "rocket", derive(FromFormField))]
183 pub enum MessageSort {
184 #[default]
186 Relevance,
187 Latest,
189 Oldest,
191 }
192
193 pub struct PushNotification {
195 pub author: String,
197 pub icon: String,
199 #[serde(skip_serializing_if = "Option::is_none")]
201 pub image: Option<String>,
202 pub body: String,
204 pub tag: String,
206 pub timestamp: u64,
208 pub url: String,
210 pub message: Message,
212 pub channel: Channel,
214 }
215
216 #[derive(Default)]
218 #[cfg_attr(feature = "validator", derive(Validate))]
219 pub struct SendableEmbed {
220 #[cfg_attr(feature = "validator", validate(length(min = 1, max = 256)))]
221 pub icon_url: Option<String>,
222 #[cfg_attr(feature = "validator", validate(length(min = 1, max = 256)))]
223 pub url: Option<String>,
224 #[cfg_attr(feature = "validator", validate(length(min = 1, max = 100)))]
225 pub title: Option<String>,
226 #[cfg_attr(feature = "validator", validate(length(min = 1, max = 2000)))]
227 pub description: Option<String>,
228 pub media: Option<String>,
229 #[cfg_attr(
230 feature = "validator",
231 validate(length(min = 1, max = 128), regex = "RE_COLOUR")
232 )]
233 pub colour: Option<String>,
234 }
235
236 pub struct ReplyIntent {
238 pub id: String,
240 pub mention: bool,
242 pub fail_if_not_exists: Option<bool>,
246 }
247
248 #[cfg_attr(feature = "validator", derive(Validate))]
250 pub struct DataMessageSend {
251 #[cfg_attr(feature = "validator", validate(length(min = 1, max = 64)))]
255 pub nonce: Option<String>,
256
257 #[cfg_attr(feature = "validator", validate(length(min = 0, max = 2000)))]
259 pub content: Option<String>,
260 pub attachments: Option<Vec<String>>,
262 pub replies: Option<Vec<ReplyIntent>>,
264 #[cfg_attr(feature = "validator", validate)]
268 pub embeds: Option<Vec<SendableEmbed>>,
269 #[cfg_attr(feature = "validator", validate)]
271 pub masquerade: Option<Masquerade>,
272 pub interactions: Option<Interactions>,
274
275 pub flags: Option<u32>,
279 }
280
281 #[cfg_attr(feature = "validator", derive(Validate))]
283 #[cfg_attr(feature = "rocket", derive(FromForm))]
284 pub struct OptionsQueryMessages {
285 #[cfg_attr(feature = "validator", validate(range(min = 1, max = 100)))]
289 pub limit: Option<i64>,
290 #[cfg_attr(feature = "validator", validate(length(min = 26, max = 26)))]
292 pub before: Option<String>,
293 #[cfg_attr(feature = "validator", validate(length(min = 26, max = 26)))]
295 pub after: Option<String>,
296 pub sort: Option<MessageSort>,
298 #[cfg_attr(feature = "validator", validate(length(min = 26, max = 26)))]
304 pub nearby: Option<String>,
305 pub include_users: Option<bool>,
307 }
308
309 #[cfg_attr(feature = "validator", derive(Validate))]
311 pub struct DataMessageSearch {
312 #[cfg_attr(feature = "validator", validate(length(min = 1, max = 64)))]
316 pub query: Option<String>,
317 pub pinned: Option<bool>,
319
320 #[cfg_attr(feature = "validator", validate(range(min = 1, max = 100)))]
322 pub limit: Option<i64>,
323 #[cfg_attr(feature = "validator", validate(length(min = 26, max = 26)))]
325 pub before: Option<String>,
326 #[cfg_attr(feature = "validator", validate(length(min = 26, max = 26)))]
328 pub after: Option<String>,
329 #[cfg_attr(feature = "serde", serde(default = "MessageSort::default"))]
333 pub sort: MessageSort,
334 pub include_users: Option<bool>,
336 }
337
338 #[cfg_attr(feature = "validator", derive(Validate))]
340 pub struct DataEditMessage {
341 #[cfg_attr(feature = "validator", validate(length(min = 1, max = 2000)))]
343 pub content: Option<String>,
344 #[cfg_attr(feature = "validator", validate(length(min = 0, max = 10)))]
346 pub embeds: Option<Vec<SendableEmbed>>,
347 }
348
349 #[cfg_attr(
351 feature = "validator",
352 cfg_attr(feature = "validator", derive(Validate))
353 )]
354 pub struct OptionsBulkDelete {
355 #[validate(length(min = 1, max = 100))]
357 pub ids: Vec<String>,
358 }
359
360 #[cfg_attr(feature = "rocket", derive(FromForm))]
362 pub struct OptionsUnreact {
363 pub user_id: Option<String>,
365 pub remove_all: Option<bool>,
367 }
368
369 #[repr(u32)]
371 pub enum MessageFlags {
372 SuppressNotifications = 1,
374 MentionsEveryone = 2,
376 MentionsOnline = 3,
379 }
380
381 pub enum FieldsMessage {
383 Pinned,
384 }
385);
386
387pub enum MessageAuthor<'a> {
389 User(&'a User),
390 Webhook(&'a Webhook),
391 System {
392 username: &'a str,
393 avatar: Option<&'a str>,
394 },
395}
396
397impl Interactions {
398 pub fn is_default(&self) -> bool {
400 !self.restrict_reactions && self.reactions.is_none()
401 }
402}
403
404impl MessageAuthor<'_> {
405 pub fn id(&self) -> &str {
406 match self {
407 MessageAuthor::User(user) => &user.id,
408 MessageAuthor::Webhook(webhook) => &webhook.id,
409 MessageAuthor::System { .. } => "00000000000000000000000000",
410 }
411 }
412
413 pub fn avatar(&self) -> Option<&str> {
414 match self {
415 MessageAuthor::User(user) => user.avatar.as_ref().map(|file| file.id.as_str()),
416 MessageAuthor::Webhook(webhook) => webhook.avatar.as_ref().map(|file| file.id.as_str()),
417 MessageAuthor::System { avatar, .. } => *avatar,
418 }
419 }
420
421 pub fn username(&self) -> &str {
422 match self {
423 MessageAuthor::User(user) => &user.username,
424 MessageAuthor::Webhook(webhook) => &webhook.name,
425 MessageAuthor::System { username, .. } => username,
426 }
427 }
428}
429
430impl From<SystemMessage> for String {
431 fn from(s: SystemMessage) -> String {
432 match s {
433 SystemMessage::Text { content } => content,
434 SystemMessage::UserAdded { .. } => "User added to the channel.".to_string(),
435 SystemMessage::UserRemove { .. } => "User removed from the channel.".to_string(),
436 SystemMessage::UserJoined { .. } => "User joined the channel.".to_string(),
437 SystemMessage::UserLeft { .. } => "User left the channel.".to_string(),
438 SystemMessage::UserKicked { .. } => "User kicked from the channel.".to_string(),
439 SystemMessage::UserBanned { .. } => "User banned from the channel.".to_string(),
440 SystemMessage::ChannelRenamed { .. } => "Channel renamed.".to_string(),
441 SystemMessage::ChannelDescriptionChanged { .. } => {
442 "Channel description changed.".to_string()
443 }
444 SystemMessage::ChannelIconChanged { .. } => "Channel icon changed.".to_string(),
445 SystemMessage::ChannelOwnershipChanged { .. } => {
446 "Channel ownership changed.".to_string()
447 }
448 SystemMessage::MessagePinned { .. } => "Message pinned.".to_string(),
449 SystemMessage::MessageUnpinned { .. } => "Message unpinned.".to_string(),
450 SystemMessage::CallStarted { .. } => "Call started.".to_string(),
451 }
452 }
453}
454
455impl PushNotification {
456 pub async fn from(msg: Message, author: Option<MessageAuthor<'_>>, channel: Channel) -> Self {
458 let config = config().await;
459
460 let icon = if let Some(author) = &author {
461 if let Some(avatar) = author.avatar() {
462 format!("{}/avatars/{}", config.hosts.autumn, avatar)
463 } else {
464 format!("{}/users/{}/default_avatar", config.hosts.api, author.id())
465 }
466 } else {
467 format!("{}/assets/logo.png", config.hosts.app)
468 };
469
470 let image = msg.attachments.as_ref().and_then(|attachments| {
471 attachments
472 .first()
473 .map(|v| format!("{}/attachments/{}", config.hosts.autumn, v.id))
474 });
475
476 let body = if let Some(ref sys) = msg.system {
477 sys.clone().into()
478 } else if let Some(ref text) = msg.content {
479 text.clone()
480 } else if let Some(text) = msg.embeds.as_ref().and_then(|embeds| match embeds.first() {
481 Some(Embed::Image(_)) => Some("Sent an image".to_string()),
482 Some(Embed::Video(_)) => Some("Sent a video".to_string()),
483 Some(Embed::Text(e)) => e
484 .description
485 .clone()
486 .or(e.title.clone().or(Some("Empty Embed".to_string()))),
487 Some(Embed::Website(e)) => e.title.clone().or(e
488 .description
489 .clone()
490 .or(e.site_name.clone().or(Some("Empty Embed".to_string())))),
491 Some(Embed::None) => Some("Empty Message".to_string()), None => Some("Empty Message".to_string()), }) {
494 text
495 } else {
496 "Empty Message".to_string()
497 };
498
499 let timestamp = SystemTime::now()
500 .duration_since(SystemTime::UNIX_EPOCH)
501 .expect("Time went backwards")
502 .as_secs();
503
504 Self {
505 author: author
506 .map(|x| x.username().to_string())
507 .unwrap_or_else(|| "Revolt".to_string()),
508 icon,
509 image,
510 body,
511 tag: channel.id().to_string(),
512 timestamp,
513 url: format!("{}/channel/{}/{}", config.hosts.app, channel.id(), msg.id),
514 message: msg,
515 channel,
516 }
517 }
518}