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 }
136
137 #[cfg_attr(feature = "validator", derive(Validate))]
139 pub struct Masquerade {
140 #[serde(skip_serializing_if = "Option::is_none")]
142 #[validate(length(min = 1, max = 32))]
143 pub name: Option<String>,
144 #[serde(skip_serializing_if = "Option::is_none")]
146 #[validate(length(min = 1, max = 256))]
147 pub avatar: Option<String>,
148 #[serde(skip_serializing_if = "Option::is_none")]
152 #[validate(length(min = 1, max = 128), regex = "RE_COLOUR")]
153 pub colour: Option<String>,
154 }
155
156 #[derive(Default)]
158 pub struct Interactions {
159 #[serde(skip_serializing_if = "Option::is_none", default)]
161 pub reactions: Option<IndexSet<String>>,
162 #[serde(skip_serializing_if = "crate::if_false", default)]
166 pub restrict_reactions: bool,
167 }
168
169 pub struct AppendMessage {
171 #[serde(skip_serializing_if = "Option::is_none")]
173 pub embeds: Option<Vec<Embed>>,
174 }
175
176 #[derive(Default)]
180 #[cfg_attr(feature = "rocket", derive(FromFormField))]
181 pub enum MessageSort {
182 #[default]
184 Relevance,
185 Latest,
187 Oldest,
189 }
190
191 pub struct PushNotification {
193 pub author: String,
195 pub icon: String,
197 #[serde(skip_serializing_if = "Option::is_none")]
199 pub image: Option<String>,
200 pub body: String,
202 pub tag: String,
204 pub timestamp: u64,
206 pub url: String,
208 pub message: Message,
210 pub channel: Channel,
212 }
213
214 #[derive(Default)]
216 #[cfg_attr(feature = "validator", derive(Validate))]
217 pub struct SendableEmbed {
218 #[cfg_attr(feature = "validator", validate(length(min = 1, max = 128)))]
219 pub icon_url: Option<String>,
220 #[cfg_attr(feature = "validator", validate(length(min = 1, max = 256)))]
221 pub url: Option<String>,
222 #[cfg_attr(feature = "validator", validate(length(min = 1, max = 100)))]
223 pub title: Option<String>,
224 #[cfg_attr(feature = "validator", validate(length(min = 1, max = 2000)))]
225 pub description: Option<String>,
226 pub media: Option<String>,
227 #[cfg_attr(
228 feature = "validator",
229 validate(length(min = 1, max = 128), regex = "RE_COLOUR")
230 )]
231 pub colour: Option<String>,
232 }
233
234 pub struct ReplyIntent {
236 pub id: String,
238 pub mention: bool,
240 pub fail_if_not_exists: Option<bool>,
244 }
245
246 #[cfg_attr(feature = "validator", derive(Validate))]
248 pub struct DataMessageSend {
249 #[cfg_attr(feature = "validator", validate(length(min = 1, max = 64)))]
253 pub nonce: Option<String>,
254
255 #[cfg_attr(feature = "validator", validate(length(min = 0, max = 2000)))]
257 pub content: Option<String>,
258 pub attachments: Option<Vec<String>>,
260 pub replies: Option<Vec<ReplyIntent>>,
262 #[cfg_attr(feature = "validator", validate)]
266 pub embeds: Option<Vec<SendableEmbed>>,
267 #[cfg_attr(feature = "validator", validate)]
269 pub masquerade: Option<Masquerade>,
270 pub interactions: Option<Interactions>,
272
273 pub flags: Option<u32>,
277 }
278
279 #[cfg_attr(feature = "validator", derive(Validate))]
281 #[cfg_attr(feature = "rocket", derive(FromForm))]
282 pub struct OptionsQueryMessages {
283 #[cfg_attr(feature = "validator", validate(range(min = 1, max = 100)))]
287 pub limit: Option<i64>,
288 #[cfg_attr(feature = "validator", validate(length(min = 26, max = 26)))]
290 pub before: Option<String>,
291 #[cfg_attr(feature = "validator", validate(length(min = 26, max = 26)))]
293 pub after: Option<String>,
294 pub sort: Option<MessageSort>,
296 #[cfg_attr(feature = "validator", validate(length(min = 26, max = 26)))]
302 pub nearby: Option<String>,
303 pub include_users: Option<bool>,
305 }
306
307 #[cfg_attr(feature = "validator", derive(Validate))]
309 pub struct DataMessageSearch {
310 #[cfg_attr(feature = "validator", validate(length(min = 1, max = 64)))]
314 pub query: Option<String>,
315 pub pinned: Option<bool>,
317
318 #[cfg_attr(feature = "validator", validate(range(min = 1, max = 100)))]
320 pub limit: Option<i64>,
321 #[cfg_attr(feature = "validator", validate(length(min = 26, max = 26)))]
323 pub before: Option<String>,
324 #[cfg_attr(feature = "validator", validate(length(min = 26, max = 26)))]
326 pub after: Option<String>,
327 #[cfg_attr(feature = "serde", serde(default = "MessageSort::default"))]
331 pub sort: MessageSort,
332 pub include_users: Option<bool>,
334 }
335
336 #[cfg_attr(feature = "validator", derive(Validate))]
338 pub struct DataEditMessage {
339 #[cfg_attr(feature = "validator", validate(length(min = 1, max = 2000)))]
341 pub content: Option<String>,
342 #[cfg_attr(feature = "validator", validate(length(min = 0, max = 10)))]
344 pub embeds: Option<Vec<SendableEmbed>>,
345 }
346
347 #[cfg_attr(
349 feature = "validator",
350 cfg_attr(feature = "validator", derive(Validate))
351 )]
352 pub struct OptionsBulkDelete {
353 #[validate(length(min = 1, max = 100))]
355 pub ids: Vec<String>,
356 }
357
358 #[cfg_attr(feature = "rocket", derive(FromForm))]
360 pub struct OptionsUnreact {
361 pub user_id: Option<String>,
363 pub remove_all: Option<bool>,
365 }
366
367 #[repr(u32)]
369 pub enum MessageFlags {
370 SuppressNotifications = 1,
372 MentionsEveryone = 2,
374 MentionsOnline = 3,
377 }
378
379 pub enum FieldsMessage {
381 Pinned,
382 }
383);
384
385pub enum MessageAuthor<'a> {
387 User(&'a User),
388 Webhook(&'a Webhook),
389 System {
390 username: &'a str,
391 avatar: Option<&'a str>,
392 },
393}
394
395impl Interactions {
396 pub fn is_default(&self) -> bool {
398 !self.restrict_reactions && self.reactions.is_none()
399 }
400}
401
402impl MessageAuthor<'_> {
403 pub fn id(&self) -> &str {
404 match self {
405 MessageAuthor::User(user) => &user.id,
406 MessageAuthor::Webhook(webhook) => &webhook.id,
407 MessageAuthor::System { .. } => "00000000000000000000000000",
408 }
409 }
410
411 pub fn avatar(&self) -> Option<&str> {
412 match self {
413 MessageAuthor::User(user) => user.avatar.as_ref().map(|file| file.id.as_str()),
414 MessageAuthor::Webhook(webhook) => webhook.avatar.as_ref().map(|file| file.id.as_str()),
415 MessageAuthor::System { avatar, .. } => *avatar,
416 }
417 }
418
419 pub fn username(&self) -> &str {
420 match self {
421 MessageAuthor::User(user) => &user.username,
422 MessageAuthor::Webhook(webhook) => &webhook.name,
423 MessageAuthor::System { username, .. } => username,
424 }
425 }
426}
427
428impl From<SystemMessage> for String {
429 fn from(s: SystemMessage) -> String {
430 match s {
431 SystemMessage::Text { content } => content,
432 SystemMessage::UserAdded { .. } => "User added to the channel.".to_string(),
433 SystemMessage::UserRemove { .. } => "User removed from the channel.".to_string(),
434 SystemMessage::UserJoined { .. } => "User joined the channel.".to_string(),
435 SystemMessage::UserLeft { .. } => "User left the channel.".to_string(),
436 SystemMessage::UserKicked { .. } => "User kicked from the channel.".to_string(),
437 SystemMessage::UserBanned { .. } => "User banned from the channel.".to_string(),
438 SystemMessage::ChannelRenamed { .. } => "Channel renamed.".to_string(),
439 SystemMessage::ChannelDescriptionChanged { .. } => {
440 "Channel description changed.".to_string()
441 }
442 SystemMessage::ChannelIconChanged { .. } => "Channel icon changed.".to_string(),
443 SystemMessage::ChannelOwnershipChanged { .. } => {
444 "Channel ownership changed.".to_string()
445 }
446 SystemMessage::MessagePinned { .. } => "Message pinned.".to_string(),
447 SystemMessage::MessageUnpinned { .. } => "Message unpinned.".to_string(),
448 }
449 }
450}
451
452impl PushNotification {
453 pub async fn from(msg: Message, author: Option<MessageAuthor<'_>>, channel: Channel) -> Self {
455 let config = config().await;
456
457 let icon = if let Some(author) = &author {
458 if let Some(avatar) = author.avatar() {
459 format!("{}/avatars/{}", config.hosts.autumn, avatar)
460 } else {
461 format!("{}/users/{}/default_avatar", config.hosts.api, author.id())
462 }
463 } else {
464 format!("{}/assets/logo.png", config.hosts.app)
465 };
466
467 let image = msg.attachments.as_ref().and_then(|attachments| {
468 attachments
469 .first()
470 .map(|v| format!("{}/attachments/{}", config.hosts.autumn, v.id))
471 });
472
473 let body = if let Some(ref sys) = msg.system {
474 sys.clone().into()
475 } else if let Some(ref text) = msg.content {
476 text.clone()
477 } else if let Some(text) = msg.embeds.as_ref().and_then(|embeds| match embeds.first() {
478 Some(Embed::Image(_)) => Some("Sent an image".to_string()),
479 Some(Embed::Video(_)) => Some("Sent a video".to_string()),
480 Some(Embed::Text(e)) => e
481 .description
482 .clone()
483 .or(e.title.clone().or(Some("Empty Embed".to_string()))),
484 Some(Embed::Website(e)) => e.title.clone().or(e
485 .description
486 .clone()
487 .or(e.site_name.clone().or(Some("Empty Embed".to_string())))),
488 Some(Embed::None) => Some("Empty Message".to_string()), None => Some("Empty Message".to_string()), }) {
491 text
492 } else {
493 "Empty Message".to_string()
494 };
495
496 let timestamp = SystemTime::now()
497 .duration_since(SystemTime::UNIX_EPOCH)
498 .expect("Time went backwards")
499 .as_secs();
500
501 Self {
502 author: author
503 .map(|x| x.username().to_string())
504 .unwrap_or_else(|| "Revolt".to_string()),
505 icon,
506 image,
507 body,
508 tag: channel.id().to_string(),
509 timestamp,
510 url: format!("{}/channel/{}/{}", config.hosts.app, channel.id(), msg.id),
511 message: msg,
512 channel,
513 }
514 }
515}