robespierre_models/
servers.rs

1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4
5use crate::{
6    autumn::Attachment,
7    channels::ChannelPermissions,
8    id::{CategoryId, ChannelId, MemberId, RoleId, ServerId, UserId},
9};
10
11/*
12Types
13*/
14
15// https://github.com/revoltchat/api/blob/097f40e37108cd3a1816b1c2cc69a137ae317069/types/Servers.ts#L4-L8
16
17pub type MemberCompositeKey = MemberId;
18
19// https://github.com/revoltchat/api/blob/097f40e37108cd3a1816b1c2cc69a137ae317069/types/Servers.ts#L10-L18
20
21#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
22#[serde(deny_unknown_fields)]
23pub struct Member {
24    #[serde(rename = "_id")]
25    pub id: MemberCompositeKey,
26    #[serde(default, skip_serializing_if = "Option::is_none")]
27    pub nickname: Option<String>,
28    #[serde(default, skip_serializing_if = "Option::is_none")]
29    pub avatar: Option<Attachment>,
30    #[serde(default, skip_serializing_if = "Vec::is_empty")]
31    pub roles: Vec<RoleId>,
32}
33
34// https://github.com/revoltchat/api/blob/097f40e37108cd3a1816b1c2cc69a137ae317069/types/Servers.ts#L20-L23
35
36#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
37#[serde(deny_unknown_fields)]
38pub struct Ban {
39    #[serde(rename = "_id")]
40    pub id: MemberCompositeKey,
41    #[serde(default, skip_serializing_if = "Option::is_none")]
42    pub reason: Option<String>,
43}
44
45// https://github.com/revoltchat/api/blob/097f40e37108cd3a1816b1c2cc69a137ae317069/types/Servers.ts#L25-L31
46
47pub type PermissionTuple = (ServerPermissions, ChannelPermissions);
48
49bitflags::bitflags! {
50    #[derive(Serialize, Deserialize)]
51    #[serde(transparent)]
52    #[doc = "Server permissions"]
53    pub struct ServerPermissions: u32 {
54        const VIEW = 0b00000000000000000000000000000001;            // 1
55        const MANAGE_ROLES = 0b00000000000000000000000000000010;   // 2
56        const MANAGE_CHANNELS = 0b00000000000000000000000000000100;  // 4
57        const MANAGE_SERVER = 0b00000000000000000000000000001000;    // 8
58        const KICK_MEMBERS = 0b00000000000000000000000000010000;     // 16
59        const BAN_MEMBERS = 0b00000000000000000000000000100000;      // 32
60        const CHANGE_NICKNAME = 0b00000000000000000001000000000000;  // 4096
61        const MANAGE_NICKNAMES = 0b00000000000000000010000000000000; // 8192
62        const CHANGE_AVATAR = 0b00000000000000000100000000000000;    // 16382
63        const REMOVE_AVATARS = 0b00000000000000001000000000000000;   // 32768
64    }
65}
66
67// https://github.com/revoltchat/api/blob/097f40e37108cd3a1816b1c2cc69a137ae317069/types/Servers.ts#L33-L46
68
69pub type Color = String;
70
71// https://github.com/revoltchat/api/blob/097f40e37108cd3a1816b1c2cc69a137ae317069/types/Servers.ts#L48-L71
72
73#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
74#[serde(deny_unknown_fields)]
75pub struct Role {
76    /// The name of the role.
77    pub name: String,
78    /// The permissions the role has.
79    pub permissions: PermissionTuple,
80    /// The color
81    /// "Valid html color"
82    /// - documentation
83    ///
84    /// The documentation says that this is untrusted input,
85    /// and should not be inserted anywhere.
86    /// Example usage:
87    /// ```js
88    /// document.body.style.color = role.color;
89    /// ```
90    #[serde(rename = "colour", default, skip_serializing_if = "Option::is_none")]
91    pub color: Option<Color>,
92    /// Whether this role is hoisted.
93    #[serde(default, skip_serializing_if = "Option::is_none")]
94    pub hoist: Option<bool>,
95    /// The rank of this role.
96    ///
97    /// The higher the rank, the lower in the role hierarchy it will be.
98    #[serde(default, skip_serializing_if = "Option::is_none")]
99    pub rank: Option<usize>,
100}
101
102// https://github.com/revoltchat/api/blob/097f40e37108cd3a1816b1c2cc69a137ae317069/types/Servers.ts#L73
103
104#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
105#[serde(deny_unknown_fields)]
106pub struct RoleInformation {
107    pub name: String,
108    #[serde(rename = "colour", default, skip_serializing_if = "Option::is_none")]
109    pub color: Option<Color>,
110    #[serde(default, skip_serializing_if = "Option::is_none")]
111    pub hoist: Option<bool>,
112    #[serde(default, skip_serializing_if = "Option::is_none")]
113    pub rank: Option<usize>,
114}
115
116// https://github.com/revoltchat/api/blob/097f40e37108cd3a1816b1c2cc69a137ae317069/types/Servers.ts#L75-L81
117
118#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
119#[serde(deny_unknown_fields)]
120pub struct Category {
121    pub id: CategoryId,
122    pub title: String,
123    pub channels: Vec<ChannelId>,
124}
125
126// https://github.com/revoltchat/api/blob/097f40e37108cd3a1816b1c2cc69a137ae317069/types/Servers.ts#L83-L92
127
128#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
129#[serde(deny_unknown_fields)]
130pub struct SystemMessageChannels {
131    #[serde(default, skip_serializing_if = "Option::is_none")]
132    pub user_joined: Option<ChannelId>,
133    #[serde(default, skip_serializing_if = "Option::is_none")]
134    pub user_left: Option<ChannelId>,
135    #[serde(default, skip_serializing_if = "Option::is_none")]
136    pub user_kicked: Option<ChannelId>,
137    #[serde(default, skip_serializing_if = "Option::is_none")]
138    pub user_banned: Option<ChannelId>,
139}
140
141// https://github.com/revoltchat/api/blob/097f40e37108cd3a1816b1c2cc69a137ae317069/types/Servers.ts#L94-L160
142
143/// A server.
144#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
145#[serde(deny_unknown_fields)]
146pub struct Server {
147    #[serde(rename = "_id")]
148    pub id: ServerId,
149    #[serde(default, skip_serializing_if = "Option::is_none")]
150    pub nonce: Option<String>,
151    pub owner: UserId,
152    pub name: String,
153    #[serde(default, skip_serializing_if = "Option::is_none")]
154    pub description: Option<String>,
155    pub channels: Vec<ChannelId>,
156    #[serde(default, skip_serializing_if = "Vec::is_empty")]
157    pub categories: Vec<Category>,
158    #[serde(default, skip_serializing_if = "Option::is_none")]
159    pub system_messages: Option<SystemMessageChannels>,
160    #[serde(default, skip_serializing_if = "Option::is_none")]
161    pub roles: Option<RolesObject>,
162    pub default_permissions: PermissionTuple,
163    #[serde(default, skip_serializing_if = "Option::is_none")]
164    pub icon: Option<Attachment>,
165    #[serde(default, skip_serializing_if = "Option::is_none")]
166    pub banner: Option<Attachment>,
167    #[serde(default, skip_serializing_if = "Option::is_none")]
168    pub nsfw: Option<bool>,
169    #[serde(default, skip_serializing_if = "Option::is_none")]
170    pub flags: Option<ServerFlags>,
171}
172
173// https://github.com/revoltchat/api/blob/b6eead8fa9228ac06b7e694ede434ba336fa5b12/types/Servers.ts#L161-L166
174
175bitflags::bitflags! {
176    #[derive(Serialize, Deserialize)]
177    #[serde(transparent)]
178    pub struct ServerFlags: u32 {
179        const OFFICIAL_REVOLT_SERVER = 1;
180        const VERIFIED_COMMUNITY_SERVER = 2;
181    }
182}
183
184/*
185Extra
186*/
187
188/// A member where all the fields are optional, and can be treated as
189/// a patch that can be applied to a [`Member`].
190#[derive(Serialize, Deserialize, Default, Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
191#[serde(deny_unknown_fields)]
192pub struct PartialMember {
193    #[serde(rename = "_id", default, skip_serializing_if = "Option::is_none")]
194    pub id: Option<MemberId>,
195    #[serde(default, skip_serializing_if = "Option::is_none")]
196    pub nickname: Option<String>,
197    #[serde(default, skip_serializing_if = "Option::is_none")]
198    pub avatar: Option<Attachment>,
199    #[serde(default, skip_serializing_if = "Option::is_none")]
200    pub roles: Option<Vec<RoleId>>,
201}
202
203impl PartialMember {
204    /// Treat self as a patch and apply it to member.
205    pub fn patch(self, member: &mut Member) {
206        let PartialMember {
207            id: pid,
208            nickname: pnickname,
209            avatar: pavatar,
210            roles: proles,
211        } = self;
212        let Member {
213            id,
214            nickname,
215            avatar,
216            roles,
217        } = member;
218
219        if let Some(pid) = pid {
220            *id = pid;
221        }
222        if let Some(pnickname) = pnickname {
223            *nickname = Some(pnickname);
224        }
225        if let Some(pavatar) = pavatar {
226            *avatar = Some(pavatar);
227        }
228        if let Some(proles) = proles {
229            *roles = proles;
230        }
231    }
232}
233
234/// A member field, that can be used to unset a field in a [`Member`].
235#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
236pub enum MemberField {
237    Nickname,
238    Avatar,
239}
240
241impl MemberField {
242    /// Treats self as a patch and removes the field from the member.
243    pub fn remove_patch(self, member: &mut Member) {
244        match self {
245            Self::Nickname => member.nickname = None,
246            Self::Avatar => member.avatar = None,
247        }
248    }
249}
250
251/// A "roles object", as a map of (key=[`RoleId`], value=[`Role`]) elements,
252/// as that is how roles are represented within a [`Server`] object.
253#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
254#[serde(transparent)]
255pub struct RolesObject(HashMap<RoleId, Role>);
256
257impl RolesObject {
258    pub fn iter(&self) -> RolesIter {
259        RolesIter(self.0.iter())
260    }
261
262    pub fn get(&self, id: &RoleId) -> Option<&Role> {
263        self.0.get(id)
264    }
265
266    pub fn patch_role(&mut self, role_id: &RoleId, patch: PartialRole, remove: Option<RoleField>) {
267        if let Some(refr) = self.0.get_mut(role_id) {
268            patch.patch(refr);
269
270            if let Some(remove) = remove {
271                remove.remove_patch(refr);
272            }
273        }
274    }
275
276    pub fn remove(&mut self, id: &RoleId) {
277        self.0.remove(id);
278    }
279}
280
281pub struct RolesIter<'a>(std::collections::hash_map::Iter<'a, RoleId, Role>);
282
283impl<'a> Iterator for RolesIter<'a> {
284    type Item = (&'a RoleId, &'a Role);
285
286    fn next(&mut self) -> Option<Self::Item> {
287        self.0.next()
288    }
289}
290
291/// A role where all the fields are optional, and can be used to
292/// describe a patch applied to a role.
293#[derive(Serialize, Deserialize, Debug, Default, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
294#[serde(deny_unknown_fields)]
295pub struct PartialRole {
296    #[serde(default, skip_serializing_if = "Option::is_none")]
297    pub name: Option<String>,
298    #[serde(default, skip_serializing_if = "Option::is_none")]
299    pub permissions: Option<(ServerPermissions, ChannelPermissions)>,
300    #[serde(rename = "colour", default, skip_serializing_if = "Option::is_none")]
301    pub color: Option<String>,
302    #[serde(default, skip_serializing_if = "Option::is_none")]
303    pub hoist: Option<bool>,
304    #[serde(default, skip_serializing_if = "Option::is_none")]
305    pub rank: Option<usize>,
306}
307
308impl PartialRole {
309    /// Treat self as a patch and apply it to role.
310    pub fn patch(self, role: &mut Role) {
311        let PartialRole {
312            name: pname,
313            permissions: ppermissions,
314            color: pcolor,
315            hoist: phoist,
316            rank: prank,
317        } = self;
318        let Role {
319            name,
320            permissions,
321            color,
322            hoist,
323            rank,
324        } = role;
325
326        if let Some(pname) = pname {
327            *name = pname;
328        }
329        if let Some(ppermissions) = ppermissions {
330            *permissions = ppermissions;
331        }
332        if let Some(pcolor) = pcolor {
333            *color = Some(pcolor);
334        }
335        if let Some(phoist) = phoist {
336            *hoist = Some(phoist);
337        }
338        if let Some(prank) = prank {
339            *rank = Some(prank);
340        }
341    }
342}
343
344/// A role field
345#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
346pub enum RoleField {
347    #[serde(rename = "Colour")]
348    Color,
349}
350
351impl RoleField {
352    /// Treats this role as a patch and removes the field from the role.
353    pub fn remove_patch(self, role: &mut Role) {
354        match self {
355            Self::Color => role.color = None,
356        }
357    }
358}
359
360/// A server where all the fields are optional, and so can be
361/// treated as a patch that can be applied to a [`Server`].
362#[derive(Serialize, Deserialize, Default, Debug, Clone, Eq, PartialEq)]
363#[serde(deny_unknown_fields)]
364pub struct PartialServer {
365    #[serde(rename = "_id", default, skip_serializing_if = "Option::is_none")]
366    pub id: Option<ServerId>,
367    #[serde(default, skip_serializing_if = "Option::is_none")]
368    pub nonce: Option<String>,
369    #[serde(default, skip_serializing_if = "Option::is_none")]
370    pub owner: Option<UserId>,
371    #[serde(default, skip_serializing_if = "Option::is_none")]
372    pub name: Option<String>,
373    #[serde(default, skip_serializing_if = "Option::is_none")]
374    pub description: Option<String>,
375    #[serde(default, skip_serializing_if = "Option::is_none")]
376    pub channels: Option<Vec<ChannelId>>,
377    #[serde(default, skip_serializing_if = "Option::is_none")]
378    pub categories: Option<Vec<Category>>,
379    #[serde(default, skip_serializing_if = "Option::is_none")]
380    pub system_messages: Option<SystemMessageChannels>,
381    #[serde(default, skip_serializing_if = "Option::is_none")]
382    pub roles: Option<RolesObject>,
383    #[serde(default, skip_serializing_if = "Option::is_none")]
384    pub default_permissions: Option<(ServerPermissions, ChannelPermissions)>,
385    #[serde(default, skip_serializing_if = "Option::is_none")]
386    pub icon: Option<Attachment>,
387    #[serde(default, skip_serializing_if = "Option::is_none")]
388    pub banner: Option<Attachment>,
389    #[serde(default, skip_serializing_if = "Option::is_none")]
390    pub nsfw: Option<bool>,
391    #[serde(default, skip_serializing_if = "Option::is_none")]
392    pub flags: Option<ServerFlags>,
393}
394
395impl PartialServer {
396    /// Treats self as a patch and applies it to server.
397    pub fn patch(self, serv: &mut Server) {
398        let PartialServer {
399            id: pid,
400            nonce: pnonce,
401            owner: powner,
402            name: pname,
403            description: pdescription,
404            channels: pchannels,
405            categories: pcategories,
406            system_messages: psystem_messages,
407            roles: proles,
408            default_permissions: pdefault_permissions,
409            icon: picon,
410            banner: pbanner,
411            nsfw: pnsfw,
412            flags: pflags,
413        } = self;
414        let Server {
415            id,
416            nonce,
417            owner,
418            name,
419            description,
420            channels,
421            categories,
422            system_messages,
423            roles,
424            default_permissions,
425            icon,
426            banner,
427            nsfw,
428            flags,
429        } = serv;
430
431        if let Some(pid) = pid {
432            *id = pid;
433        }
434        if let Some(pnonce) = pnonce {
435            *nonce = Some(pnonce);
436        }
437        if let Some(powner) = powner {
438            *owner = powner;
439        }
440        if let Some(pname) = pname {
441            *name = pname;
442        }
443        if let Some(pdescription) = pdescription {
444            *description = Some(pdescription);
445        }
446        if let Some(pchannels) = pchannels {
447            *channels = pchannels;
448        }
449        if let Some(pcategories) = pcategories {
450            *categories = pcategories;
451        }
452        if let Some(psystem_messages) = psystem_messages {
453            *system_messages = Some(psystem_messages);
454        }
455        if let Some(proles) = proles {
456            *roles = Some(proles);
457        }
458        if let Some(pdefault_permissions) = pdefault_permissions {
459            *default_permissions = pdefault_permissions;
460        }
461        if let Some(picon) = picon {
462            *icon = Some(picon);
463        }
464        if let Some(pbanner) = pbanner {
465            *banner = Some(pbanner);
466        }
467        if let Some(pnsfw) = pnsfw {
468            *nsfw = Some(pnsfw);
469        }
470        if let Some(pflags) = pflags {
471            *flags = Some(pflags);
472        }
473    }
474}
475
476/// A server field, that can be used to unset a field in a [`Server`].
477#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
478pub enum ServerField {
479    Icon,
480    Banner,
481    Description,
482}
483
484impl ServerField {
485    /// Treats self as a patch and removes the field from the server.
486    pub fn remove_patch(self, server: &mut Server) {
487        match self {
488            Self::Icon => server.icon = None,
489            Self::Banner => server.banner = None,
490            Self::Description => server.description = None,
491        }
492    }
493}