6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -1548,9 +1548,9 @@ checksum = "90f97a5f38dd3ccfbe7aa80f4a0c00930f21b922c74195be0201c51028f22dcf"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.9.1"
|
version = "1.9.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
|
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg 1.1.0",
|
"autocfg 1.1.0",
|
||||||
"hashbrown 0.12.1",
|
"hashbrown 0.12.1",
|
||||||
@@ -2883,6 +2883,7 @@ dependencies = [
|
|||||||
"authifier",
|
"authifier",
|
||||||
"bson",
|
"bson",
|
||||||
"futures",
|
"futures",
|
||||||
|
"indexmap",
|
||||||
"iso8601-timestamp 0.2.11",
|
"iso8601-timestamp 0.2.11",
|
||||||
"log",
|
"log",
|
||||||
"mongodb",
|
"mongodb",
|
||||||
@@ -2949,6 +2950,7 @@ dependencies = [
|
|||||||
name = "revolt-models"
|
name = "revolt-models"
|
||||||
version = "0.6.5"
|
version = "0.6.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
"iso8601-timestamp 0.2.11",
|
"iso8601-timestamp 0.2.11",
|
||||||
"revolt-permissions",
|
"revolt-permissions",
|
||||||
"revolt_optional_struct",
|
"revolt_optional_struct",
|
||||||
|
|||||||
@@ -3,29 +3,32 @@ name = "revolt-database"
|
|||||||
version = "0.6.5"
|
version = "0.6.5"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
authors = [ "Paul Makles <me@insrt.uk>" ]
|
authors = ["Paul Makles <me@insrt.uk>"]
|
||||||
description = "Revolt Backend: Database Implementation"
|
description = "Revolt Backend: Database Implementation"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# Databases
|
# Databases
|
||||||
mongodb = [ "dep:mongodb", "bson" ]
|
mongodb = ["dep:mongodb", "bson"]
|
||||||
|
|
||||||
# ... Other
|
# ... Other
|
||||||
async-std-runtime = [ "async-std" ]
|
async-std-runtime = ["async-std"]
|
||||||
rocket-impl = [ "rocket", "schemars" ]
|
rocket-impl = ["rocket", "schemars"]
|
||||||
redis-is-patched = [ "revolt-presence/redis-is-patched" ]
|
redis-is-patched = ["revolt-presence/redis-is-patched"]
|
||||||
|
|
||||||
# Default Features
|
# Default Features
|
||||||
default = [ "mongodb", "async-std-runtime" ]
|
default = ["mongodb", "async-std-runtime"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Core
|
# Core
|
||||||
revolt-result = { version = "0.6.5", path = "../result" }
|
revolt-result = { version = "0.6.5", path = "../result" }
|
||||||
revolt-models = { version = "0.6.5", path = "../models" }
|
revolt-models = { version = "0.6.5", path = "../models" }
|
||||||
revolt-presence = { version = "0.6.5", path = "../presence" }
|
revolt-presence = { version = "0.6.5", path = "../presence" }
|
||||||
revolt-permissions = { version = "0.6.5", path = "../permissions", features = [ "serde", "bson" ] }
|
revolt-permissions = { version = "0.6.5", path = "../permissions", features = [
|
||||||
|
"serde",
|
||||||
|
"bson",
|
||||||
|
] }
|
||||||
|
|
||||||
# Utility
|
# Utility
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
@@ -33,6 +36,7 @@ rand = "0.8.5"
|
|||||||
ulid = "1.0.0"
|
ulid = "1.0.0"
|
||||||
nanoid = "0.4.0"
|
nanoid = "0.4.0"
|
||||||
once_cell = "1.17"
|
once_cell = "1.17"
|
||||||
|
indexmap = "1.9.1"
|
||||||
|
|
||||||
# Serialisation
|
# Serialisation
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
@@ -61,7 +65,9 @@ async-std = { version = "1.8.0", features = ["attributes"], optional = true }
|
|||||||
|
|
||||||
# Rocket Impl
|
# Rocket Impl
|
||||||
schemars = { version = "0.8.8", optional = true }
|
schemars = { version = "0.8.8", optional = true }
|
||||||
rocket = { version = "0.5.0-rc.2", default-features = false, features = ["json"], optional = true }
|
rocket = { version = "0.5.0-rc.2", default-features = false, features = [
|
||||||
|
"json",
|
||||||
|
], optional = true }
|
||||||
|
|
||||||
# Authifier
|
# Authifier
|
||||||
authifier = { version = "1.0" }
|
authifier = { version = "1.0" }
|
||||||
|
|||||||
5
crates/core/database/src/models/messages/mod.rs
Normal file
5
crates/core/database/src/models/messages/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
mod model;
|
||||||
|
mod ops;
|
||||||
|
|
||||||
|
pub use model::*;
|
||||||
|
pub use ops::*;
|
||||||
212
crates/core/database/src/models/messages/model.rs
Normal file
212
crates/core/database/src/models/messages/model.rs
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
use indexmap::{IndexMap, IndexSet};
|
||||||
|
use iso8601_timestamp::Timestamp;
|
||||||
|
use revolt_models::v0::{Embed, MessageSort, MessageWebhook};
|
||||||
|
|
||||||
|
use crate::File;
|
||||||
|
|
||||||
|
auto_derived_partial!(
|
||||||
|
/// Message
|
||||||
|
pub struct Message {
|
||||||
|
/// Unique Id
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
/// Unique value generated by client sending this message
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub nonce: Option<String>,
|
||||||
|
/// Id of the channel this message was sent in
|
||||||
|
pub channel: String,
|
||||||
|
/// Id of the user or webhook that sent this message
|
||||||
|
pub author: String,
|
||||||
|
/// The webhook that sent this message
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub webhook: Option<MessageWebhook>,
|
||||||
|
/// Message content
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub content: Option<String>,
|
||||||
|
/// System message
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub system: Option<SystemMessage>,
|
||||||
|
/// Array of attachments
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub attachments: Option<Vec<File>>,
|
||||||
|
/// Time at which this message was last edited
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub edited: Option<Timestamp>,
|
||||||
|
/// Attached embeds to this message
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub embeds: Option<Vec<Embed>>,
|
||||||
|
/// Array of user ids mentioned in this message
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub mentions: Option<Vec<String>>,
|
||||||
|
/// Array of message ids this message is replying to
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub replies: Option<Vec<String>>,
|
||||||
|
/// Hashmap of emoji IDs to array of user IDs
|
||||||
|
#[serde(skip_serializing_if = "IndexMap::is_empty", default)]
|
||||||
|
pub reactions: IndexMap<String, IndexSet<String>>,
|
||||||
|
/// Information about how this message should be interacted with
|
||||||
|
#[serde(skip_serializing_if = "Interactions::is_default", default)]
|
||||||
|
pub interactions: Interactions,
|
||||||
|
/// Name and / or avatar overrides for this message
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub masquerade: Option<Masquerade>,
|
||||||
|
},
|
||||||
|
"PartialMessage"
|
||||||
|
);
|
||||||
|
|
||||||
|
auto_derived!(
|
||||||
|
/// System Event
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum SystemMessage {
|
||||||
|
#[serde(rename = "text")]
|
||||||
|
Text { content: String },
|
||||||
|
#[serde(rename = "user_added")]
|
||||||
|
UserAdded { id: String, by: String },
|
||||||
|
#[serde(rename = "user_remove")]
|
||||||
|
UserRemove { id: String, by: String },
|
||||||
|
#[serde(rename = "user_joined")]
|
||||||
|
UserJoined { id: String },
|
||||||
|
#[serde(rename = "user_left")]
|
||||||
|
UserLeft { id: String },
|
||||||
|
#[serde(rename = "user_kicked")]
|
||||||
|
UserKicked { id: String },
|
||||||
|
#[serde(rename = "user_banned")]
|
||||||
|
UserBanned { id: String },
|
||||||
|
#[serde(rename = "channel_renamed")]
|
||||||
|
ChannelRenamed { name: String, by: String },
|
||||||
|
#[serde(rename = "channel_description_changed")]
|
||||||
|
ChannelDescriptionChanged { by: String },
|
||||||
|
#[serde(rename = "channel_icon_changed")]
|
||||||
|
ChannelIconChanged { by: String },
|
||||||
|
#[serde(rename = "channel_ownership_changed")]
|
||||||
|
ChannelOwnershipChanged { from: String, to: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Name and / or avatar override information
|
||||||
|
pub struct Masquerade {
|
||||||
|
/// Replace the display name shown on this message
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub name: Option<String>,
|
||||||
|
/// Replace the avatar shown on this message (URL to image file)
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub avatar: Option<String>,
|
||||||
|
/// Replace the display role colour shown on this message
|
||||||
|
///
|
||||||
|
/// Must have `ManageRole` permission to use
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub colour: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information to guide interactions on this message
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Interactions {
|
||||||
|
/// Reactions which should always appear and be distinct
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||||
|
pub reactions: Option<IndexSet<String>>,
|
||||||
|
/// Whether reactions should be restricted to the given list
|
||||||
|
///
|
||||||
|
/// Can only be set to true if reactions list is of at least length 1
|
||||||
|
#[serde(skip_serializing_if = "crate::if_false", default)]
|
||||||
|
pub restrict_reactions: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appended Information
|
||||||
|
pub struct AppendMessage {
|
||||||
|
/// Additional embeds to include in this message
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub embeds: Option<Vec<Embed>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Message Time Period
|
||||||
|
///
|
||||||
|
/// Filter and sort messages by time
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum MessageTimePeriod {
|
||||||
|
Relative {
|
||||||
|
/// Message id to search around
|
||||||
|
///
|
||||||
|
/// Specifying 'nearby' ignores 'before', 'after' and 'sort'.
|
||||||
|
/// It will also take half of limit rounded as the limits to each side.
|
||||||
|
/// It also fetches the message ID specified.
|
||||||
|
nearby: String,
|
||||||
|
},
|
||||||
|
Absolute {
|
||||||
|
/// Message id before which messages should be fetched
|
||||||
|
before: Option<String>,
|
||||||
|
/// Message id after which messages should be fetched
|
||||||
|
after: Option<String>,
|
||||||
|
/// Message sort direction
|
||||||
|
sort: Option<MessageSort>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Message Filter
|
||||||
|
pub struct MessageFilter {
|
||||||
|
/// Parent channel ID
|
||||||
|
pub channel: Option<String>,
|
||||||
|
/// Message author ID
|
||||||
|
pub author: Option<String>,
|
||||||
|
/// Search query
|
||||||
|
pub query: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Message Query
|
||||||
|
pub struct MessageQuery {
|
||||||
|
/// Maximum number of messages to fetch
|
||||||
|
///
|
||||||
|
/// For fetching nearby messages, this is \`(limit + 1)\`.
|
||||||
|
pub limit: Option<i64>,
|
||||||
|
/// Filter to apply
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub filter: MessageFilter,
|
||||||
|
/// Time period to fetch
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub time_period: MessageTimePeriod,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
#[allow(clippy::disallowed_methods)]
|
||||||
|
impl Message {}
|
||||||
|
|
||||||
|
impl Interactions {
|
||||||
|
/// Validate interactions info is correct
|
||||||
|
/* pub async fn validate(
|
||||||
|
&self,
|
||||||
|
db: &Database,
|
||||||
|
permissions: &mut PermissionCalculator<'_>,
|
||||||
|
) -> Result<()> {
|
||||||
|
if let Some(reactions) = &self.reactions {
|
||||||
|
permissions.throw_permission(db, Permission::React).await?;
|
||||||
|
|
||||||
|
if reactions.len() > 20 {
|
||||||
|
return Err(Error::InvalidOperation);
|
||||||
|
}
|
||||||
|
|
||||||
|
for reaction in reactions {
|
||||||
|
if !Emoji::can_use(db, reaction).await? {
|
||||||
|
return Err(Error::InvalidOperation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/// Check if we can use a given emoji to react
|
||||||
|
pub fn can_use(&self, emoji: &str) -> bool {
|
||||||
|
if self.restrict_reactions {
|
||||||
|
if let Some(reactions) = &self.reactions {
|
||||||
|
reactions.contains(emoji)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if default initialisation of fields
|
||||||
|
pub fn is_default(&self) -> bool {
|
||||||
|
!self.restrict_reactions && self.reactions.is_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
39
crates/core/database/src/models/messages/ops.rs
Normal file
39
crates/core/database/src/models/messages/ops.rs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::{AppendMessage, Message, MessageQuery, PartialMessage};
|
||||||
|
|
||||||
|
// mod mongodb;
|
||||||
|
// mod reference;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait AbstractMessages: Sync + Send {
|
||||||
|
/// Insert a new message into the database
|
||||||
|
async fn insert_message(&self, message: &Message) -> Result<()>;
|
||||||
|
|
||||||
|
/// Fetch a message by its id
|
||||||
|
async fn fetch_message(&self, id: &str) -> Result<Message>;
|
||||||
|
|
||||||
|
/// Fetch multiple messages by given query
|
||||||
|
async fn fetch_messages(&self, query: MessageQuery) -> Result<Vec<Message>>;
|
||||||
|
|
||||||
|
/// Update a given message with new information
|
||||||
|
async fn update_message(&self, id: &str, message: &PartialMessage) -> Result<()>;
|
||||||
|
|
||||||
|
/// Append information to a given message
|
||||||
|
async fn append_message(&self, id: &str, append: &AppendMessage) -> Result<()>;
|
||||||
|
|
||||||
|
/// Add a new reaction to a message
|
||||||
|
async fn add_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()>;
|
||||||
|
|
||||||
|
/// Remove a reaction from a message
|
||||||
|
async fn remove_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()>;
|
||||||
|
|
||||||
|
/// Remove reaction from a message
|
||||||
|
async fn clear_reaction(&self, id: &str, emoji: &str) -> Result<()>;
|
||||||
|
|
||||||
|
/// Delete a message from the database by its id
|
||||||
|
async fn delete_message(&self, id: &str) -> Result<()>;
|
||||||
|
|
||||||
|
/// Delete messages from a channel by their ids and corresponding channel id
|
||||||
|
async fn delete_messages(&self, channel: &str, ids: Vec<String>) -> Result<()>;
|
||||||
|
}
|
||||||
70
crates/core/database/src/models/messages/ops/mongodb.rs
Normal file
70
crates/core/database/src/models/messages/ops/mongodb.rs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
use bson::Document;
|
||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::Emoji;
|
||||||
|
use crate::MongoDb;
|
||||||
|
|
||||||
|
use super::AbstractEmojis;
|
||||||
|
|
||||||
|
static COL: &str = "emojis";
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractEmojis for MongoDb {
|
||||||
|
/// Insert emoji into database.
|
||||||
|
async fn insert_emoji(&self, emoji: &Emoji) -> Result<()> {
|
||||||
|
query!(self, insert_one, COL, &emoji).map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch an emoji by its id
|
||||||
|
async fn fetch_emoji(&self, id: &str) -> Result<Emoji> {
|
||||||
|
query!(self, find_one_by_id, COL, id)?.ok_or_else(|| create_error!(NotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch emoji by their parent id
|
||||||
|
async fn fetch_emoji_by_parent_id(&self, parent_id: &str) -> Result<Vec<Emoji>> {
|
||||||
|
query!(
|
||||||
|
self,
|
||||||
|
find_one,
|
||||||
|
COL,
|
||||||
|
doc! {
|
||||||
|
"parent.id": parent_id
|
||||||
|
}
|
||||||
|
)?
|
||||||
|
.ok_or_else(|| create_error!(NotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch emoji by their parent ids
|
||||||
|
async fn fetch_emoji_by_parent_ids(&self, parent_ids: &[String]) -> Result<Vec<Emoji>> {
|
||||||
|
query!(
|
||||||
|
self,
|
||||||
|
find,
|
||||||
|
COL,
|
||||||
|
doc! {
|
||||||
|
"parent.id": {
|
||||||
|
"$in": parent_ids
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Detach an emoji by its id
|
||||||
|
async fn detach_emoji(&self, emoji: &Emoji) -> Result<()> {
|
||||||
|
self.col::<Document>(COL)
|
||||||
|
.update_one(
|
||||||
|
doc! {
|
||||||
|
"_id": &emoji.id
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$set": {
|
||||||
|
"parent": {
|
||||||
|
"type": "Detached"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(|_| create_database_error!("update_one", COL))
|
||||||
|
}
|
||||||
|
}
|
||||||
67
crates/core/database/src/models/messages/ops/reference.rs
Normal file
67
crates/core/database/src/models/messages/ops/reference.rs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::Emoji;
|
||||||
|
use crate::EmojiParent;
|
||||||
|
use crate::ReferenceDb;
|
||||||
|
|
||||||
|
use super::AbstractEmojis;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractEmojis for ReferenceDb {
|
||||||
|
/// Insert emoji into database.
|
||||||
|
async fn insert_emoji(&self, emoji: &Emoji) -> Result<()> {
|
||||||
|
let mut emojis = self.emojis.lock().await;
|
||||||
|
if emojis.contains_key(&emoji.id) {
|
||||||
|
Err(create_database_error!("insert", "emoji"))
|
||||||
|
} else {
|
||||||
|
emojis.insert(emoji.id.to_string(), emoji.clone());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch an emoji by its id
|
||||||
|
async fn fetch_emoji(&self, id: &str) -> Result<Emoji> {
|
||||||
|
let emojis = self.emojis.lock().await;
|
||||||
|
emojis
|
||||||
|
.get(id)
|
||||||
|
.cloned()
|
||||||
|
.ok_or_else(|| create_error!(NotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch emoji by their parent id
|
||||||
|
async fn fetch_emoji_by_parent_id(&self, parent_id: &str) -> Result<Vec<Emoji>> {
|
||||||
|
let emojis = self.emojis.lock().await;
|
||||||
|
Ok(emojis
|
||||||
|
.values()
|
||||||
|
.filter(|emoji| match &emoji.parent {
|
||||||
|
EmojiParent::Server { id } => id == parent_id,
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch emoji by their parent ids
|
||||||
|
async fn fetch_emoji_by_parent_ids(&self, parent_ids: &[String]) -> Result<Vec<Emoji>> {
|
||||||
|
let emojis = self.emojis.lock().await;
|
||||||
|
Ok(emojis
|
||||||
|
.values()
|
||||||
|
.filter(|emoji| match &emoji.parent {
|
||||||
|
EmojiParent::Server { id } => parent_ids.contains(id),
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Detach an emoji by its id
|
||||||
|
async fn detach_emoji(&self, emoji: &Emoji) -> Result<()> {
|
||||||
|
let mut emojis = self.emojis.lock().await;
|
||||||
|
if let Some(bot) = emojis.get_mut(&emoji.id) {
|
||||||
|
bot.parent = EmojiParent::Detached;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ mod channel_webhooks;
|
|||||||
mod channels;
|
mod channels;
|
||||||
mod emojis;
|
mod emojis;
|
||||||
mod files;
|
mod files;
|
||||||
|
mod messages;
|
||||||
mod ratelimit_events;
|
mod ratelimit_events;
|
||||||
mod server_bans;
|
mod server_bans;
|
||||||
mod server_members;
|
mod server_members;
|
||||||
@@ -21,6 +22,7 @@ pub use channel_webhooks::*;
|
|||||||
pub use channels::*;
|
pub use channels::*;
|
||||||
pub use emojis::*;
|
pub use emojis::*;
|
||||||
pub use files::*;
|
pub use files::*;
|
||||||
|
pub use messages::*;
|
||||||
pub use ratelimit_events::*;
|
pub use ratelimit_events::*;
|
||||||
pub use server_bans::*;
|
pub use server_bans::*;
|
||||||
pub use server_members::*;
|
pub use server_members::*;
|
||||||
|
|||||||
@@ -294,6 +294,95 @@ impl From<crate::Metadata> for Metadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<crate::Message> for Message {
|
||||||
|
fn from(value: crate::Message) -> Self {
|
||||||
|
Message {
|
||||||
|
id: value.id,
|
||||||
|
nonce: value.nonce,
|
||||||
|
channel: value.channel,
|
||||||
|
author: value.author,
|
||||||
|
webhook: value.webhook,
|
||||||
|
content: value.content,
|
||||||
|
system: value.system.map(|system| system.into()),
|
||||||
|
attachments: value
|
||||||
|
.attachments
|
||||||
|
.map(|v| v.into_iter().map(|f| f.into()).collect()),
|
||||||
|
edited: value.edited,
|
||||||
|
embeds: value.embeds,
|
||||||
|
mentions: value.mentions,
|
||||||
|
replies: value.replies,
|
||||||
|
reactions: value.reactions,
|
||||||
|
interactions: value.interactions.into(),
|
||||||
|
masquerade: value.masquerade.map(|masq| masq.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::PartialMessage> for PartialMessage {
|
||||||
|
fn from(value: crate::PartialMessage) -> Self {
|
||||||
|
PartialMessage {
|
||||||
|
id: value.id,
|
||||||
|
nonce: value.nonce,
|
||||||
|
channel: value.channel,
|
||||||
|
author: value.author,
|
||||||
|
webhook: value.webhook,
|
||||||
|
content: value.content,
|
||||||
|
system: value.system.map(|system| system.into()),
|
||||||
|
attachments: value
|
||||||
|
.attachments
|
||||||
|
.map(|v| v.into_iter().map(|f| f.into()).collect()),
|
||||||
|
edited: value.edited,
|
||||||
|
embeds: value.embeds,
|
||||||
|
mentions: value.mentions,
|
||||||
|
replies: value.replies,
|
||||||
|
reactions: value.reactions,
|
||||||
|
interactions: value.interactions.map(|interactions| interactions.into()),
|
||||||
|
masquerade: value.masquerade.map(|masq| masq.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::SystemMessage> for SystemMessage {
|
||||||
|
fn from(value: crate::SystemMessage) -> Self {
|
||||||
|
match value {
|
||||||
|
crate::SystemMessage::ChannelDescriptionChanged { by } => {
|
||||||
|
Self::ChannelDescriptionChanged { by }
|
||||||
|
}
|
||||||
|
crate::SystemMessage::ChannelIconChanged { by } => Self::ChannelIconChanged { by },
|
||||||
|
crate::SystemMessage::ChannelOwnershipChanged { from, to } => {
|
||||||
|
Self::ChannelOwnershipChanged { from, to }
|
||||||
|
}
|
||||||
|
crate::SystemMessage::ChannelRenamed { name, by } => Self::ChannelRenamed { name, by },
|
||||||
|
crate::SystemMessage::Text { content } => Self::Text { content },
|
||||||
|
crate::SystemMessage::UserAdded { id, by } => Self::UserAdded { id, by },
|
||||||
|
crate::SystemMessage::UserBanned { id } => Self::UserBanned { id },
|
||||||
|
crate::SystemMessage::UserJoined { id } => Self::UserJoined { id },
|
||||||
|
crate::SystemMessage::UserKicked { id } => Self::UserKicked { id },
|
||||||
|
crate::SystemMessage::UserLeft { id } => Self::UserLeft { id },
|
||||||
|
crate::SystemMessage::UserRemove { id, by } => Self::UserRemove { id, by },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::Interactions> for Interactions {
|
||||||
|
fn from(value: crate::Interactions) -> Self {
|
||||||
|
Interactions {
|
||||||
|
reactions: value.reactions,
|
||||||
|
restrict_reactions: value.restrict_reactions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::Masquerade> for Masquerade {
|
||||||
|
fn from(value: crate::Masquerade) -> Self {
|
||||||
|
Masquerade {
|
||||||
|
name: value.name,
|
||||||
|
avatar: value.avatar,
|
||||||
|
colour: value.colour,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<crate::ServerBan> for ServerBan {
|
impl From<crate::ServerBan> for ServerBan {
|
||||||
fn from(value: crate::ServerBan) -> Self {
|
fn from(value: crate::ServerBan) -> Self {
|
||||||
ServerBan {
|
ServerBan {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ default = ["serde", "partials"]
|
|||||||
revolt-permissions = { version = "0.6.5", path = "../permissions" }
|
revolt-permissions = { version = "0.6.5", path = "../permissions" }
|
||||||
|
|
||||||
# Serialisation
|
# Serialisation
|
||||||
|
indexmap = "1.9.3"
|
||||||
revolt_optional_struct = { version = "0.2.0", optional = true }
|
revolt_optional_struct = { version = "0.2.0", optional = true }
|
||||||
serde = { version = "1", features = ["derive"], optional = true }
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
iso8601-timestamp = { version = "0.2.11", features = ["schema", "bson"] }
|
iso8601-timestamp = { version = "0.2.11", features = ["schema", "bson"] }
|
||||||
|
|||||||
160
crates/core/models/src/v0/embeds.rs
Normal file
160
crates/core/models/src/v0/embeds.rs
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
use super::File;
|
||||||
|
|
||||||
|
auto_derived!(
|
||||||
|
/// Image positioning and size
|
||||||
|
pub enum ImageSize {
|
||||||
|
/// Show large preview at the bottom of the embed
|
||||||
|
Large,
|
||||||
|
/// Show small preview to the side of the embed
|
||||||
|
Preview,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Image
|
||||||
|
pub struct Image {
|
||||||
|
/// URL to the original image
|
||||||
|
pub url: String,
|
||||||
|
/// Width of the image
|
||||||
|
pub width: isize,
|
||||||
|
/// Height of the image
|
||||||
|
pub height: isize,
|
||||||
|
/// Positioning and size
|
||||||
|
pub size: ImageSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Video
|
||||||
|
pub struct Video {
|
||||||
|
/// URL to the original video
|
||||||
|
pub url: String,
|
||||||
|
/// Width of the video
|
||||||
|
pub width: isize,
|
||||||
|
/// Height of the video
|
||||||
|
pub height: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Type of remote Twitch content
|
||||||
|
pub enum TwitchType {
|
||||||
|
Channel,
|
||||||
|
Video,
|
||||||
|
Clip,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Type of remote Lightspeed.tv content
|
||||||
|
pub enum LightspeedType {
|
||||||
|
Channel,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Type of remote Bandcamp content
|
||||||
|
pub enum BandcampType {
|
||||||
|
Album,
|
||||||
|
Track,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information about special remote content
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum Special {
|
||||||
|
/// No remote content
|
||||||
|
None,
|
||||||
|
/// Content hint that this contains a GIF
|
||||||
|
///
|
||||||
|
/// Use metadata to find video or image to play
|
||||||
|
GIF,
|
||||||
|
/// YouTube video
|
||||||
|
YouTube {
|
||||||
|
id: String,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
timestamp: Option<String>,
|
||||||
|
},
|
||||||
|
/// Lightspeed.tv stream
|
||||||
|
Lightspeed {
|
||||||
|
content_type: LightspeedType,
|
||||||
|
id: String,
|
||||||
|
},
|
||||||
|
/// Twitch stream or clip
|
||||||
|
Twitch {
|
||||||
|
content_type: TwitchType,
|
||||||
|
id: String,
|
||||||
|
},
|
||||||
|
/// Spotify track
|
||||||
|
Spotify { content_type: String, id: String },
|
||||||
|
/// Soundcloud track
|
||||||
|
Soundcloud,
|
||||||
|
/// Bandcamp track
|
||||||
|
Bandcamp {
|
||||||
|
content_type: BandcampType,
|
||||||
|
id: String,
|
||||||
|
},
|
||||||
|
/// Streamable Video
|
||||||
|
Streamable { id: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Website metadata
|
||||||
|
pub struct WebsiteMetadata {
|
||||||
|
/// Direct URL to web page
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
url: Option<String>,
|
||||||
|
/// Original direct URL
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
original_url: Option<String>,
|
||||||
|
/// Remote content
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
special: Option<Special>,
|
||||||
|
|
||||||
|
/// Title of website
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
title: Option<String>,
|
||||||
|
/// Description of website
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
description: Option<String>,
|
||||||
|
/// Embedded image
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
image: Option<Image>,
|
||||||
|
/// Embedded video
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
video: Option<Video>,
|
||||||
|
|
||||||
|
// #[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
// opengraph_type: Option<String>,
|
||||||
|
/// Site name
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
site_name: Option<String>,
|
||||||
|
/// URL to site icon
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
icon_url: Option<String>,
|
||||||
|
/// CSS Colour
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
colour: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Text Embed
|
||||||
|
pub struct Text {
|
||||||
|
/// URL to icon
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub icon_url: Option<String>,
|
||||||
|
/// URL for title
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub url: Option<String>,
|
||||||
|
/// Title of text embed
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub title: Option<String>,
|
||||||
|
/// Description of text embed
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub description: Option<String>,
|
||||||
|
/// ID of uploaded autumn file
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub media: Option<File>,
|
||||||
|
/// CSS Colour
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub colour: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Embed
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum Embed {
|
||||||
|
Website(WebsiteMetadata),
|
||||||
|
Image(Image),
|
||||||
|
Video(Video),
|
||||||
|
Text(Text),
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
);
|
||||||
139
crates/core/models/src/v0/messages.rs
Normal file
139
crates/core/models/src/v0/messages.rs
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
use indexmap::{IndexMap, IndexSet};
|
||||||
|
use iso8601_timestamp::Timestamp;
|
||||||
|
|
||||||
|
use super::{Embed, File, MessageWebhook};
|
||||||
|
|
||||||
|
auto_derived_partial!(
|
||||||
|
/// Message
|
||||||
|
pub struct Message {
|
||||||
|
/// Unique Id
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
/// Unique value generated by client sending this message
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub nonce: Option<String>,
|
||||||
|
/// Id of the channel this message was sent in
|
||||||
|
pub channel: String,
|
||||||
|
/// Id of the user or webhook that sent this message
|
||||||
|
pub author: String,
|
||||||
|
/// The webhook that sent this message
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub webhook: Option<MessageWebhook>,
|
||||||
|
/// Message content
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub content: Option<String>,
|
||||||
|
/// System message
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub system: Option<SystemMessage>,
|
||||||
|
/// Array of attachments
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub attachments: Option<Vec<File>>,
|
||||||
|
/// Time at which this message was last edited
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub edited: Option<Timestamp>,
|
||||||
|
/// Attached embeds to this message
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub embeds: Option<Vec<Embed>>,
|
||||||
|
/// Array of user ids mentioned in this message
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub mentions: Option<Vec<String>>,
|
||||||
|
/// Array of message ids this message is replying to
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub replies: Option<Vec<String>>,
|
||||||
|
/// Hashmap of emoji IDs to array of user IDs
|
||||||
|
#[serde(skip_serializing_if = "IndexMap::is_empty", default)]
|
||||||
|
pub reactions: IndexMap<String, IndexSet<String>>,
|
||||||
|
/// Information about how this message should be interacted with
|
||||||
|
#[serde(skip_serializing_if = "Interactions::is_default", default)]
|
||||||
|
pub interactions: Interactions,
|
||||||
|
/// Name and / or avatar overrides for this message
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub masquerade: Option<Masquerade>,
|
||||||
|
},
|
||||||
|
"PartialMessage"
|
||||||
|
);
|
||||||
|
|
||||||
|
auto_derived!(
|
||||||
|
/// System Event
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum SystemMessage {
|
||||||
|
#[serde(rename = "text")]
|
||||||
|
Text { content: String },
|
||||||
|
#[serde(rename = "user_added")]
|
||||||
|
UserAdded { id: String, by: String },
|
||||||
|
#[serde(rename = "user_remove")]
|
||||||
|
UserRemove { id: String, by: String },
|
||||||
|
#[serde(rename = "user_joined")]
|
||||||
|
UserJoined { id: String },
|
||||||
|
#[serde(rename = "user_left")]
|
||||||
|
UserLeft { id: String },
|
||||||
|
#[serde(rename = "user_kicked")]
|
||||||
|
UserKicked { id: String },
|
||||||
|
#[serde(rename = "user_banned")]
|
||||||
|
UserBanned { id: String },
|
||||||
|
#[serde(rename = "channel_renamed")]
|
||||||
|
ChannelRenamed { name: String, by: String },
|
||||||
|
#[serde(rename = "channel_description_changed")]
|
||||||
|
ChannelDescriptionChanged { by: String },
|
||||||
|
#[serde(rename = "channel_icon_changed")]
|
||||||
|
ChannelIconChanged { by: String },
|
||||||
|
#[serde(rename = "channel_ownership_changed")]
|
||||||
|
ChannelOwnershipChanged { from: String, to: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Name and / or avatar override information
|
||||||
|
pub struct Masquerade {
|
||||||
|
/// Replace the display name shown on this message
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub name: Option<String>,
|
||||||
|
/// Replace the avatar shown on this message (URL to image file)
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub avatar: Option<String>,
|
||||||
|
/// Replace the display role colour shown on this message
|
||||||
|
///
|
||||||
|
/// Must have `ManageRole` permission to use
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub colour: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information to guide interactions on this message
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Interactions {
|
||||||
|
/// Reactions which should always appear and be distinct
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||||
|
pub reactions: Option<IndexSet<String>>,
|
||||||
|
/// Whether reactions should be restricted to the given list
|
||||||
|
///
|
||||||
|
/// Can only be set to true if reactions list is of at least length 1
|
||||||
|
#[serde(skip_serializing_if = "crate::if_false", default)]
|
||||||
|
pub restrict_reactions: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appended Information
|
||||||
|
pub struct AppendMessage {
|
||||||
|
/// Additional embeds to include in this message
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub embeds: Option<Vec<Embed>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Message Sort
|
||||||
|
///
|
||||||
|
/// Sort used for retrieving messages
|
||||||
|
#[derive(Default)]
|
||||||
|
pub enum MessageSort {
|
||||||
|
/// Sort by the most relevant messages
|
||||||
|
#[default]
|
||||||
|
Relevance,
|
||||||
|
/// Sort by the newest messages first
|
||||||
|
Latest,
|
||||||
|
/// Sort by the oldest messages first
|
||||||
|
Oldest,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
impl Interactions {
|
||||||
|
/// Check if default initialisation of fields
|
||||||
|
pub fn is_default(&self) -> bool {
|
||||||
|
!self.restrict_reactions && self.reactions.is_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,8 +3,10 @@ mod channel_invites;
|
|||||||
mod channel_unreads;
|
mod channel_unreads;
|
||||||
mod channel_webhooks;
|
mod channel_webhooks;
|
||||||
mod channels;
|
mod channels;
|
||||||
|
mod embeds;
|
||||||
mod emojis;
|
mod emojis;
|
||||||
mod files;
|
mod files;
|
||||||
|
mod messages;
|
||||||
mod server_bans;
|
mod server_bans;
|
||||||
mod server_members;
|
mod server_members;
|
||||||
mod servers;
|
mod servers;
|
||||||
@@ -16,8 +18,10 @@ pub use channel_invites::*;
|
|||||||
pub use channel_unreads::*;
|
pub use channel_unreads::*;
|
||||||
pub use channel_webhooks::*;
|
pub use channel_webhooks::*;
|
||||||
pub use channels::*;
|
pub use channels::*;
|
||||||
|
pub use embeds::*;
|
||||||
pub use emojis::*;
|
pub use emojis::*;
|
||||||
pub use files::*;
|
pub use files::*;
|
||||||
|
pub use messages::*;
|
||||||
pub use server_bans::*;
|
pub use server_bans::*;
|
||||||
pub use server_members::*;
|
pub use server_members::*;
|
||||||
pub use servers::*;
|
pub use servers::*;
|
||||||
|
|||||||
@@ -269,7 +269,6 @@ pub struct DataMessageSend {
|
|||||||
#[validate(length(min = 0, max = 2000))]
|
#[validate(length(min = 0, max = 2000))]
|
||||||
pub content: Option<String>,
|
pub content: Option<String>,
|
||||||
/// Attachments to include in message
|
/// Attachments to include in message
|
||||||
|
|
||||||
pub attachments: Option<Vec<String>>,
|
pub attachments: Option<Vec<String>>,
|
||||||
/// Messages to reply to
|
/// Messages to reply to
|
||||||
pub replies: Option<Vec<Reply>>,
|
pub replies: Option<Vec<Reply>>,
|
||||||
|
|||||||
Reference in New Issue
Block a user