Skip to main content

revolt_database/models/channels/
model.rs

1#![allow(deprecated)]
2use std::{borrow::Cow, collections::HashMap};
3
4use redis_kiss::get_connection;
5use revolt_config::config;
6use revolt_models::v0::{self, MessageAuthor};
7use revolt_permissions::OverrideField;
8use revolt_result::Result;
9use serde::{Deserialize, Serialize};
10use ulid::Ulid;
11
12use crate::{
13    events::client::EventV1, Database, File, PartialServer, Server, SystemMessage, User, AMQP,
14};
15
16#[cfg(feature = "mongodb")]
17use crate::IntoDocumentPath;
18
19auto_derived!(
20    #[serde(tag = "channel_type")]
21    pub enum Channel {
22        /// Personal "Saved Notes" channel which allows users to save messages
23        SavedMessages {
24            /// Unique Id
25            #[serde(rename = "_id")]
26            id: String,
27            /// Id of the user this channel belongs to
28            user: String,
29        },
30        /// Direct message channel between two users
31        DirectMessage {
32            /// Unique Id
33            #[serde(rename = "_id")]
34            id: String,
35
36            /// Whether this direct message channel is currently open on both sides
37            active: bool,
38            /// 2-tuple of user ids participating in direct message
39            recipients: Vec<String>,
40            /// Id of the last message sent in this channel
41            #[serde(skip_serializing_if = "Option::is_none")]
42            last_message_id: Option<String>,
43        },
44        /// Group channel between 1 or more participants
45        Group {
46            /// Unique Id
47            #[serde(rename = "_id")]
48            id: String,
49
50            /// Display name of the channel
51            name: String,
52            /// User id of the owner of the group
53            owner: String,
54            /// Channel description
55            #[serde(skip_serializing_if = "Option::is_none")]
56            description: Option<String>,
57            /// Array of user ids participating in channel
58            recipients: Vec<String>,
59
60            /// Custom icon attachment
61            #[serde(skip_serializing_if = "Option::is_none")]
62            icon: Option<File>,
63            /// Id of the last message sent in this channel
64            #[serde(skip_serializing_if = "Option::is_none")]
65            last_message_id: Option<String>,
66
67            /// Permissions assigned to members of this group
68            /// (does not apply to the owner of the group)
69            #[serde(skip_serializing_if = "Option::is_none")]
70            permissions: Option<i64>,
71
72            /// Whether this group is marked as not safe for work
73            #[serde(skip_serializing_if = "crate::if_false", default)]
74            nsfw: bool,
75        },
76        /// Text channel belonging to a server
77        TextChannel {
78            /// Unique Id
79            #[serde(rename = "_id")]
80            id: String,
81            /// Id of the server this channel belongs to
82            server: String,
83
84            /// Display name of the channel
85            name: String,
86            /// Channel description
87            #[serde(skip_serializing_if = "Option::is_none")]
88            description: Option<String>,
89
90            /// Custom icon attachment
91            #[serde(skip_serializing_if = "Option::is_none")]
92            icon: Option<File>,
93            /// Id of the last message sent in this channel
94            #[serde(skip_serializing_if = "Option::is_none")]
95            last_message_id: Option<String>,
96
97            /// Default permissions assigned to users in this channel
98            #[serde(skip_serializing_if = "Option::is_none")]
99            default_permissions: Option<OverrideField>,
100            /// Permissions assigned based on role to this channel
101            #[serde(
102                default = "HashMap::<String, OverrideField>::new",
103                skip_serializing_if = "HashMap::<String, OverrideField>::is_empty"
104            )]
105            role_permissions: HashMap<String, OverrideField>,
106
107            /// Whether this channel is marked as not safe for work
108            #[serde(skip_serializing_if = "crate::if_false", default)]
109            nsfw: bool,
110
111            /// Voice Information for when this channel is also a voice channel
112            #[serde(skip_serializing_if = "Option::is_none")]
113            voice: Option<VoiceInformation>,
114
115            /// The channel's slowmode delay in seconds
116            #[serde(skip_serializing_if = "Option::is_none")]
117            slowmode: Option<u64>,
118        },
119    }
120
121    #[derive(Default)]
122    pub struct VoiceInformation {
123        /// Maximium amount of users allowed in the voice channel at once
124        #[serde(skip_serializing_if = "Option::is_none")]
125        pub max_users: Option<usize>,
126    }
127);
128
129auto_derived!(
130    #[derive(Default)]
131    pub struct PartialChannel {
132        #[serde(skip_serializing_if = "Option::is_none")]
133        pub name: Option<String>,
134        #[serde(skip_serializing_if = "Option::is_none")]
135        pub owner: Option<String>,
136        #[serde(skip_serializing_if = "Option::is_none")]
137        pub description: Option<String>,
138        #[serde(skip_serializing_if = "Option::is_none")]
139        pub icon: Option<File>,
140        #[serde(skip_serializing_if = "Option::is_none")]
141        pub nsfw: Option<bool>,
142        #[serde(skip_serializing_if = "Option::is_none")]
143        pub active: Option<bool>,
144        #[serde(skip_serializing_if = "Option::is_none")]
145        pub permissions: Option<i64>,
146        #[serde(skip_serializing_if = "Option::is_none")]
147        pub role_permissions: Option<HashMap<String, OverrideField>>,
148        #[serde(skip_serializing_if = "Option::is_none")]
149        pub default_permissions: Option<OverrideField>,
150        #[serde(skip_serializing_if = "Option::is_none")]
151        pub last_message_id: Option<String>,
152        #[serde(skip_serializing_if = "Option::is_none")]
153        pub voice: Option<VoiceInformation>,
154        #[serde(skip_serializing_if = "Option::is_none")]
155        pub slowmode: Option<u64>,
156    }
157
158    /// Optional fields on channel object
159    pub enum FieldsChannel {
160        Description,
161        Icon,
162        DefaultPermissions,
163        Voice,
164    }
165);
166
167#[allow(clippy::disallowed_methods)]
168impl Channel {
169    /* /// Create a channel
170    pub async fn create(&self, db: &Database) -> Result<()> {
171        db.insert_channel(self).await?;
172
173        let event = EventV1::ChannelCreate(self.clone().into());
174        match self {
175            Self::SavedMessages { user, .. } => event.private(user.clone()).await,
176            Self::DirectMessage { recipients, .. } | Self::Group { recipients, .. } => {
177                for recipient in recipients {
178                    event.clone().private(recipient.clone()).await;
179                }
180            }
181            Self::TextChannel { server, .. } | Self::VoiceChannel { server, .. } => {
182                event.p(server.clone()).await;
183            }
184        }
185
186        Ok(())
187    }*/
188
189    /// Create a new server channel
190    pub async fn create_server_channel(
191        db: &Database,
192        server: &mut Server,
193        data: v0::DataCreateServerChannel,
194        update_server: bool,
195    ) -> Result<Channel> {
196        let config = config().await;
197        if server.channels.len() > config.features.limits.global.server_channels {
198            return Err(create_error!(TooManyChannels {
199                max: config.features.limits.global.server_channels,
200            }));
201        };
202
203        let id = ulid::Ulid::new().to_string();
204        let channel = match data.channel_type {
205            v0::LegacyServerChannelType::Text => Channel::TextChannel {
206                id: id.clone(),
207                server: server.id.to_owned(),
208                name: data.name,
209                description: data.description,
210                icon: None,
211                last_message_id: None,
212                default_permissions: None,
213                role_permissions: HashMap::new(),
214                nsfw: data.nsfw.unwrap_or(false),
215                voice: data.voice.map(|voice| voice.into()),
216                slowmode: None,
217            },
218            v0::LegacyServerChannelType::Voice => Channel::TextChannel {
219                id: id.clone(),
220                server: server.id.to_owned(),
221                name: data.name,
222                description: data.description,
223                icon: None,
224                last_message_id: None,
225                default_permissions: None,
226                role_permissions: HashMap::new(),
227                nsfw: data.nsfw.unwrap_or(false),
228                voice: Some(data.voice.unwrap_or_default().into()),
229                slowmode: None,
230            },
231        };
232
233        db.insert_channel(&channel).await?;
234
235        if update_server {
236            server
237                .update(
238                    db,
239                    PartialServer {
240                        channels: Some([server.channels.clone(), [id].into()].concat()),
241                        ..Default::default()
242                    },
243                    vec![],
244                )
245                .await?;
246
247            EventV1::ChannelCreate(channel.clone().into())
248                .p(server.id.clone())
249                .await;
250        }
251
252        Ok(channel)
253    }
254
255    /// Create a group
256    pub async fn create_group(
257        db: &Database,
258        mut data: v0::DataCreateGroup,
259        owner_id: String,
260    ) -> Result<Channel> {
261        data.users.insert(owner_id.to_string());
262
263        let config = config().await;
264        if data.users.len() > config.features.limits.global.group_size {
265            return Err(create_error!(GroupTooLarge {
266                max: config.features.limits.global.group_size,
267            }));
268        }
269
270        let id = ulid::Ulid::new().to_string();
271
272        let icon = if let Some(icon_id) = data.icon {
273            Some(File::use_channel_icon(db, &icon_id, &id, &owner_id).await?)
274        } else {
275            None
276        };
277
278        let recipients = data.users.into_iter().collect::<Vec<String>>();
279        let channel = Channel::Group {
280            id,
281
282            name: data.name,
283            owner: owner_id,
284            description: data.description,
285            recipients: recipients.clone(),
286
287            icon,
288            last_message_id: None,
289
290            permissions: None,
291
292            nsfw: data.nsfw.unwrap_or(false),
293        };
294
295        db.insert_channel(&channel).await?;
296
297        let event = EventV1::ChannelCreate(channel.clone().into());
298        for recipient in recipients {
299            event.clone().private(recipient).await;
300        }
301
302        Ok(channel)
303    }
304
305    /// Create a DM (or return the existing one / saved messages)
306    pub async fn create_dm(db: &Database, user_a: &User, user_b: &User) -> Result<Channel> {
307        // Try to find existing channel
308        if let Ok(channel) = db.find_direct_message_channel(&user_a.id, &user_b.id).await {
309            Ok(channel)
310        } else {
311            let channel = if user_a.id == user_b.id {
312                // Create a new saved messages channel
313                Channel::SavedMessages {
314                    id: Ulid::new().to_string(),
315                    user: user_a.id.to_string(),
316                }
317            } else {
318                // Create a new DM channel
319                Channel::DirectMessage {
320                    id: Ulid::new().to_string(),
321                    active: true, // show by default
322                    recipients: vec![user_a.id.clone(), user_b.id.clone()],
323                    last_message_id: None,
324                }
325            };
326
327            db.insert_channel(&channel).await?;
328
329            if let Channel::DirectMessage { .. } = &channel {
330                let event = EventV1::ChannelCreate(channel.clone().into());
331                event.clone().private(user_a.id.clone()).await;
332                event.private(user_b.id.clone()).await;
333            };
334
335            Ok(channel)
336        }
337    }
338
339    /// Add user to a group
340    pub async fn add_user_to_group(
341        &mut self,
342        db: &Database,
343        amqp: &AMQP,
344        user: &User,
345        by_id: &str,
346    ) -> Result<()> {
347        if let Channel::Group { recipients, .. } = self {
348            if recipients.contains(&String::from(&user.id)) {
349                return Err(create_error!(AlreadyInGroup));
350            }
351
352            let config = config().await;
353            if recipients.len() >= config.features.limits.global.group_size {
354                return Err(create_error!(GroupTooLarge {
355                    max: config.features.limits.global.group_size
356                }));
357            }
358
359            recipients.push(String::from(&user.id));
360        }
361
362        match &self {
363            Channel::Group { id, .. } => {
364                db.add_user_to_group(id, &user.id).await?;
365
366                EventV1::ChannelGroupJoin {
367                    id: id.to_string(),
368                    user: user.id.to_string(),
369                }
370                .p(id.to_string())
371                .await;
372
373                SystemMessage::UserAdded {
374                    id: user.id.to_string(),
375                    by: by_id.to_string(),
376                }
377                .into_message(id.to_string())
378                .send(
379                    db,
380                    Some(amqp),
381                    MessageAuthor::System {
382                        username: &user.username,
383                        avatar: user.avatar.as_ref().map(|file| file.id.as_ref()),
384                    },
385                    None,
386                    None,
387                    self,
388                    false,
389                )
390                .await
391                .ok();
392
393                EventV1::ChannelCreate(self.clone().into())
394                    .private(user.id.to_string())
395                    .await;
396
397                Ok(())
398            }
399            _ => Err(create_error!(InvalidOperation)),
400        }
401    }
402
403    /// Map out whether it is a direct DM
404    pub fn is_direct_dm(&self) -> bool {
405        matches!(self, Channel::DirectMessage { .. })
406    }
407
408    /// Check whether has a user as a recipient
409    pub fn contains_user(&self, user_id: &str) -> bool {
410        match self {
411            Channel::Group { recipients, .. } => recipients.contains(&String::from(user_id)),
412            _ => false,
413        }
414    }
415
416    /// Get list of recipients
417    pub fn users(&self) -> Result<Vec<String>> {
418        match self {
419            Channel::Group { recipients, .. } => Ok(recipients.to_owned()),
420            _ => Err(create_error!(NotFound)),
421        }
422    }
423
424    /// Clone this channel's id
425    pub fn id(&self) -> &str {
426        match self {
427            Channel::DirectMessage { id, .. }
428            | Channel::Group { id, .. }
429            | Channel::SavedMessages { id, .. }
430            | Channel::TextChannel { id, .. } => id,
431        }
432    }
433
434    /// Clone this channel's server id
435    pub fn server(&self) -> Option<&str> {
436        match self {
437            Channel::TextChannel { server, .. } => Some(server),
438            _ => None,
439        }
440    }
441
442    /// Gets this channel's voice information
443    pub fn voice(&self) -> Option<Cow<VoiceInformation>> {
444        match self {
445            Self::DirectMessage { .. } | Self::Group { .. } => {
446                Some(Cow::Owned(VoiceInformation::default()))
447            }
448            Self::TextChannel {
449                voice: Some(voice), ..
450            } => Some(Cow::Borrowed(voice)),
451            _ => None,
452        }
453    }
454
455    /// Set role permission on a channel
456    pub async fn set_role_permission(
457        &mut self,
458        db: &Database,
459        role_id: &str,
460        permissions: OverrideField,
461    ) -> Result<()> {
462        match self {
463            Channel::TextChannel {
464                id,
465                server,
466                role_permissions,
467                ..
468            } => {
469                db.set_channel_role_permission(id, role_id, permissions)
470                    .await?;
471
472                role_permissions.insert(role_id.to_string(), permissions);
473
474                EventV1::ChannelUpdate {
475                    id: id.clone(),
476                    data: PartialChannel {
477                        role_permissions: Some(role_permissions.clone()),
478                        ..Default::default()
479                    }
480                    .into(),
481                    clear: vec![],
482                }
483                .p(server.clone())
484                .await;
485
486                Ok(())
487            }
488            _ => Err(create_error!(InvalidOperation)),
489        }
490    }
491
492    /// Update channel data
493    pub async fn update(
494        &mut self,
495        db: &Database,
496        partial: PartialChannel,
497        remove: Vec<FieldsChannel>,
498    ) -> Result<()> {
499        for field in &remove {
500            self.remove_field(field);
501        }
502
503        self.apply_options(partial.clone());
504
505        let id = self.id().to_string();
506        db.update_channel(&id, &partial, remove.clone()).await?;
507
508        EventV1::ChannelUpdate {
509            id: id.clone(),
510            data: partial.into(),
511            clear: remove.into_iter().map(|v| v.into()).collect(),
512        }
513        .p(match self {
514            Self::TextChannel { server, .. } => server.clone(),
515            _ => id,
516        })
517        .await;
518
519        Ok(())
520    }
521
522    /// Remove a field from Channel object
523    pub fn remove_field(&mut self, field: &FieldsChannel) {
524        match field {
525            FieldsChannel::Description => match self {
526                Self::Group { description, .. } | Self::TextChannel { description, .. } => {
527                    description.take();
528                }
529                _ => {}
530            },
531            FieldsChannel::Icon => match self {
532                Self::Group { icon, .. } | Self::TextChannel { icon, .. } => {
533                    icon.take();
534                }
535                _ => {}
536            },
537            FieldsChannel::DefaultPermissions => match self {
538                Self::TextChannel {
539                    default_permissions,
540                    ..
541                } => {
542                    default_permissions.take();
543                }
544                _ => {}
545            },
546            FieldsChannel::Voice => match self {
547                Self::TextChannel { voice, .. } => {
548                    voice.take();
549                }
550                _ => {}
551            },
552        }
553    }
554
555    /// Remove multiple fields from Channel object
556    pub fn remove_fields(&mut self, partial: Vec<FieldsChannel>) {
557        for field in partial {
558            self.remove_field(&field)
559        }
560    }
561
562    /// Apply partial channel to channel
563    #[allow(deprecated)]
564    pub fn apply_options(&mut self, partial: PartialChannel) {
565        match self {
566            Self::SavedMessages { .. } => {}
567            Self::DirectMessage { active, .. } => {
568                if let Some(v) = partial.active {
569                    *active = v;
570                }
571            }
572            Self::Group {
573                name,
574                owner,
575                description,
576                icon,
577                nsfw,
578                permissions,
579                ..
580            } => {
581                if let Some(v) = partial.name {
582                    *name = v;
583                }
584
585                if let Some(v) = partial.owner {
586                    *owner = v;
587                }
588
589                if let Some(v) = partial.description {
590                    description.replace(v);
591                }
592
593                if let Some(v) = partial.icon {
594                    icon.replace(v);
595                }
596
597                if let Some(v) = partial.nsfw {
598                    *nsfw = v;
599                }
600
601                if let Some(v) = partial.permissions {
602                    permissions.replace(v);
603                }
604            }
605            Self::TextChannel {
606                name,
607                description,
608                icon,
609                nsfw,
610                default_permissions,
611                role_permissions,
612                voice,
613                ..
614            } => {
615                if let Some(v) = partial.name {
616                    *name = v;
617                }
618
619                if let Some(v) = partial.description {
620                    description.replace(v);
621                }
622
623                if let Some(v) = partial.icon {
624                    icon.replace(v);
625                }
626
627                if let Some(v) = partial.nsfw {
628                    *nsfw = v;
629                }
630
631                if let Some(v) = partial.role_permissions {
632                    *role_permissions = v;
633                }
634
635                if let Some(v) = partial.default_permissions {
636                    default_permissions.replace(v);
637                }
638
639                if let Some(v) = partial.voice {
640                    voice.replace(v);
641                }
642            }
643        }
644    }
645
646    /// Acknowledge a message
647    pub async fn ack(&self, user: &str, message: &str, amqp: &AMQP) -> Result<()> {
648        EventV1::ChannelAck {
649            id: self.id().to_string(),
650            user: user.to_string(),
651            message_id: message.to_string(),
652        }
653        .private(user.to_string())
654        .await;
655
656        crate::util::acker::ack_channel(user, self.id(), message, amqp).await
657    }
658
659    /// Remove user from a group
660    pub async fn remove_user_from_group(
661        &self,
662        db: &Database,
663        amqp: &AMQP,
664        user: &User,
665        by_id: Option<&str>,
666        silent: bool,
667    ) -> Result<()> {
668        match &self {
669            Channel::Group {
670                id,
671                name,
672                owner,
673                recipients,
674                ..
675            } => {
676                if &user.id == owner {
677                    if let Some(new_owner) = recipients.iter().find(|x| *x != &user.id) {
678                        db.update_channel(
679                            id,
680                            &PartialChannel {
681                                owner: Some(new_owner.into()),
682                                ..Default::default()
683                            },
684                            vec![],
685                        )
686                        .await?;
687
688                        SystemMessage::ChannelOwnershipChanged {
689                            from: owner.to_string(),
690                            to: new_owner.to_string(),
691                        }
692                        .into_message(id.to_string())
693                        .send(
694                            db,
695                            Some(amqp),
696                            MessageAuthor::System {
697                                username: name,
698                                avatar: None,
699                            },
700                            None,
701                            None,
702                            self,
703                            false,
704                        )
705                        .await
706                        .ok();
707                    } else {
708                        return self.delete(db).await;
709                    }
710                }
711
712                db.remove_user_from_group(id, &user.id).await?;
713
714                EventV1::ChannelGroupLeave {
715                    id: id.to_string(),
716                    user: user.id.to_string(),
717                }
718                .p(id.to_string())
719                .await;
720
721                if !silent {
722                    if let Some(by) = by_id {
723                        SystemMessage::UserRemove {
724                            id: user.id.to_string(),
725                            by: by.to_string(),
726                        }
727                    } else {
728                        SystemMessage::UserLeft {
729                            id: user.id.to_string(),
730                        }
731                    }
732                    .into_message(id.to_string())
733                    .send(
734                        db,
735                        Some(amqp),
736                        MessageAuthor::System {
737                            username: &user.username,
738                            avatar: user.avatar.as_ref().map(|file| file.id.as_ref()),
739                        },
740                        None,
741                        None,
742                        self,
743                        false,
744                    )
745                    .await
746                    .ok();
747                }
748
749                Ok(())
750            }
751
752            _ => Err(create_error!(InvalidOperation)),
753        }
754    }
755
756    /// Delete a channel
757    pub async fn delete(&self, db: &Database) -> Result<()> {
758        let id = self.id().to_string();
759        EventV1::ChannelDelete { id: id.clone() }.p(id).await;
760        // TODO: missing functionality:
761        // - group invites
762        // - channels list / categories list on server
763        db.delete_channel(self).await
764    }
765}
766
767#[cfg(feature = "mongodb")]
768impl IntoDocumentPath for FieldsChannel {
769    fn as_path(&self) -> Option<&'static str> {
770        Some(match self {
771            FieldsChannel::Description => "description",
772            FieldsChannel::Icon => "icon",
773            FieldsChannel::DefaultPermissions => "default_permissions",
774            FieldsChannel::Voice => "voice",
775        })
776    }
777}
778
779#[cfg(test)]
780mod tests {
781    use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
782
783    use crate::{fixture, util::permissions::DatabasePermissionQuery};
784
785    #[async_std::test]
786    async fn permissions_group_channel() {
787        database_test!(|db| async move {
788            fixture!(db, "group_with_members",
789                owner user 0
790                member1 user 1
791                member2 user 2
792                channel channel 3);
793
794            let mut query = DatabasePermissionQuery::new(&db, &owner).channel(&channel);
795            assert!(calculate_channel_permissions(&mut query)
796                .await
797                .has_channel_permission(ChannelPermission::SendMessage));
798
799            let mut query = DatabasePermissionQuery::new(&db, &member1).channel(&channel);
800            assert!(calculate_channel_permissions(&mut query)
801                .await
802                .has_channel_permission(ChannelPermission::SendMessage));
803
804            let mut query = DatabasePermissionQuery::new(&db, &member2).channel(&channel);
805            assert!(!calculate_channel_permissions(&mut query)
806                .await
807                .has_channel_permission(ChannelPermission::SendMessage));
808        });
809    }
810
811    #[async_std::test]
812    async fn permissions_text_channel() {
813        database_test!(|db| async move {
814            fixture!(db, "server_with_roles",
815                owner user 0
816                moderator user 1
817                user user 2
818                channel channel 3);
819
820            let mut query = DatabasePermissionQuery::new(&db, &owner).channel(&channel);
821            assert!(calculate_channel_permissions(&mut query)
822                .await
823                .has_channel_permission(ChannelPermission::SendMessage));
824
825            let mut query = DatabasePermissionQuery::new(&db, &moderator).channel(&channel);
826            assert!(calculate_channel_permissions(&mut query)
827                .await
828                .has_channel_permission(ChannelPermission::SendMessage));
829
830            let mut query = DatabasePermissionQuery::new(&db, &user).channel(&channel);
831            assert!(!calculate_channel_permissions(&mut query)
832                .await
833                .has_channel_permission(ChannelPermission::SendMessage));
834        });
835    }
836}