From 7293abfc53df3a8f6a56aad4530102e491e66c3d Mon Sep 17 00:00:00 2001 From: Paul <paulmakles@gmail.com> Date: Fri, 7 May 2021 18:00:21 +0100 Subject: [PATCH] Add january support. --- Cargo.lock | 10 ++ Cargo.toml | 1 + set_version.sh | 2 +- src/database/entities/channel.rs | 12 +- src/database/entities/january.rs | 71 ++++++++++ src/database/entities/message.rs | 151 ++++++++++++--------- src/database/entities/mod.rs | 4 +- src/notifications/events.rs | 8 +- src/notifications/websocket.rs | 16 +-- src/routes/channels/delete_channel.rs | 4 +- src/routes/channels/edit_channel.rs | 4 +- src/routes/channels/group_add_member.rs | 4 +- src/routes/channels/group_remove_member.rs | 4 +- src/routes/users/add_friend.rs | 57 ++++---- src/routes/users/block_user.rs | 60 ++++---- src/routes/users/change_username.rs | 4 +- src/routes/users/edit_user.rs | 12 +- src/routes/users/remove_friend.rs | 28 ++-- src/routes/users/unblock_user.rs | 32 ++--- src/util/variables.rs | 2 + 20 files changed, 279 insertions(+), 207 deletions(-) create mode 100644 src/database/entities/january.rs diff --git a/Cargo.lock b/Cargo.lock index 6f366a9..c27ed1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1389,6 +1389,15 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" +[[package]] +name = "linkify" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1986921c3c13e81df623c66a298d4b130c061bcb98a01f5b2d3ac402b1649a7f" +dependencies = [ + "memchr", +] + [[package]] name = "lock_api" version = "0.3.4" @@ -2490,6 +2499,7 @@ dependencies = [ "impl_ops", "lazy_static", "lettre", + "linkify", "log", "many-to-many", "md5", diff --git a/Cargo.toml b/Cargo.toml index cbde4f2..8d8a3ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ ulid = "0.4.1" rand = "0.7.3" time = "0.2.16" base64 = "0.13.0" +linkify = "0.6.0" dotenv = "0.15.0" futures = "0.3.8" chrono = "0.4.15" diff --git a/set_version.sh b/set_version.sh index ffe5b03..bc53540 100755 --- a/set_version.sh +++ b/set_version.sh @@ -1,3 +1,3 @@ #!/bin/bash -export version=0.4.1-alpha.7-patch.2 +export version=0.4.1-alpha.8 echo "pub const VERSION: &str = \"${version}\";" > src/version.rs diff --git a/src/database/entities/channel.rs b/src/database/entities/channel.rs index 1c7c96a..7f61b70 100644 --- a/src/database/entities/channel.rs +++ b/src/database/entities/channel.rs @@ -94,9 +94,7 @@ impl Channel { let channel_id = self.id().to_string(); ClientboundNotification::ChannelCreate(self) - .publish(channel_id) - .await - .ok(); + .publish(channel_id); Ok(()) } @@ -108,9 +106,7 @@ impl Channel { data, clear: None } - .publish(id) - .await - .ok(); + .publish(id); Ok(()) } @@ -193,9 +189,7 @@ impl Channel { })?; ClientboundNotification::ChannelDelete { id: id.to_string() } - .publish(id.to_string()) - .await - .ok(); + .publish(id.to_string()); if let Channel::Group { icon, .. } = self { if let Some(attachment) = icon { diff --git a/src/database/entities/january.rs b/src/database/entities/january.rs new file mode 100644 index 0000000..8810908 --- /dev/null +++ b/src/database/entities/january.rs @@ -0,0 +1,71 @@ +use serde::{Serialize, Deserialize}; +use linkify::{LinkFinder, LinkKind}; +use crate::util::{result::{Error, Result}, variables::JANUARY_URL}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum MediaSize { + Large, + Preview, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Media { + pub url: String, + pub width: isize, + pub height: isize, + pub size: MediaSize, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Metadata { + #[serde(skip_serializing_if = "Option::is_none")] + title: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + description: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + image: Option<Media>, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(tag = "type")] +pub enum Embed { + Website(Metadata), + Image(Media), + None, +} + +impl Embed { + pub async fn generate(content: String) -> Result<Vec<Embed>> { + // FIXME: allow multiple links + let mut finder = LinkFinder::new(); + finder.kinds(&[LinkKind::Url]); + let links: Vec<_> = finder.links(&content).collect(); + + if links.len() == 0 { + return Err(Error::LabelMe); + } + + let link = &links[0]; + + let client = reqwest::Client::new(); + let result = client + .get(&format!("{}/embed", *JANUARY_URL)) + .query(&[("url", link.as_str())]) + .send() + .await; + + match result { + Err(_) => return Err(Error::LabelMe), + Ok(result) => match result.status() { + reqwest::StatusCode::OK => { + let res: Embed = result.json() + .await + .map_err(|_| Error::InvalidOperation)?; + + Ok(vec![ res ]) + }, + _ => return Err(Error::LabelMe), + }, + } + } +} diff --git a/src/database/entities/message.rs b/src/database/entities/message.rs index 4960b78..5e23d41 100644 --- a/src/database/entities/message.rs +++ b/src/database/entities/message.rs @@ -17,12 +17,6 @@ use web_push::{ ContentEncoding, SubscriptionInfo, VapidSignatureBuilder, WebPushClient, WebPushMessageBuilder, }; -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(tag = "type")] -pub enum MessageEmbed { - Dummy, -} - #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "type")] pub enum SystemMessage { @@ -60,7 +54,7 @@ pub struct Message { #[serde(skip_serializing_if = "Option::is_none")] pub edited: Option<DateTime>, #[serde(skip_serializing_if = "Option::is_none")] - pub embeds: Option<MessageEmbed>, + pub embeds: Option<Vec<Embed>>, } impl Message { @@ -138,11 +132,12 @@ impl Message { _ => {} } + self.process_embed(); + let enc = serde_json::to_string(&self).unwrap(); ClientboundNotification::Message(self) - .publish(channel.id().to_string()) - .await - .unwrap(); + .publish(channel.id().to_string()); + /* Web Push Test Code @@ -162,78 +157,114 @@ impl Message { _ => {} } - // Fetch their corresponding sessions. - if let Ok(mut cursor) = get_collection("accounts") - .find( - doc! { - "_id": { - "$in": target_ids + async_std::task::spawn(async move { + // Fetch their corresponding sessions. + if let Ok(mut cursor) = get_collection("accounts") + .find( + doc! { + "_id": { + "$in": target_ids + }, + "sessions.subscription": { + "$exists": true + } }, - "sessions.subscription": { - "$exists": true - } - }, - FindOptions::builder() - .projection(doc! { "sessions": 1 }) - .build(), - ) - .await - { - let mut subscriptions = vec![]; - while let Some(result) = cursor.next().await { - if let Ok(doc) = result { - if let Ok(sessions) = doc.get_array("sessions") { - for session in sessions { - if let Some(doc) = session.as_document() { - if let Ok(sub) = doc.get_document("subscription") { - let endpoint = sub.get_str("endpoint").unwrap().to_string(); - let p256dh = sub.get_str("p256dh").unwrap().to_string(); - let auth = sub.get_str("auth").unwrap().to_string(); + FindOptions::builder() + .projection(doc! { "sessions": 1 }) + .build(), + ) + .await + { + let mut subscriptions = vec![]; + while let Some(result) = cursor.next().await { + if let Ok(doc) = result { + if let Ok(sessions) = doc.get_array("sessions") { + for session in sessions { + if let Some(doc) = session.as_document() { + if let Ok(sub) = doc.get_document("subscription") { + let endpoint = sub.get_str("endpoint").unwrap().to_string(); + let p256dh = sub.get_str("p256dh").unwrap().to_string(); + let auth = sub.get_str("auth").unwrap().to_string(); - subscriptions - .push(SubscriptionInfo::new(endpoint, p256dh, auth)); + subscriptions + .push(SubscriptionInfo::new(endpoint, p256dh, auth)); + } } } } } } - } - if subscriptions.len() > 0 { - let client = WebPushClient::new(); - let key = - base64::decode_config(VAPID_PRIVATE_KEY.clone(), base64::URL_SAFE).unwrap(); + if subscriptions.len() > 0 { + let client = WebPushClient::new(); + let key = + base64::decode_config(VAPID_PRIVATE_KEY.clone(), base64::URL_SAFE).unwrap(); - for subscription in subscriptions { - let mut builder = WebPushMessageBuilder::new(&subscription).unwrap(); - let sig_builder = - VapidSignatureBuilder::from_pem(std::io::Cursor::new(&key), &subscription) - .unwrap(); - let signature = sig_builder.build().unwrap(); - builder.set_vapid_signature(signature); - builder.set_payload(ContentEncoding::AesGcm, enc.as_bytes()); - let m = builder.build().unwrap(); - client.send(m).await.ok(); + for subscription in subscriptions { + let mut builder = WebPushMessageBuilder::new(&subscription).unwrap(); + let sig_builder = + VapidSignatureBuilder::from_pem(std::io::Cursor::new(&key), &subscription) + .unwrap(); + let signature = sig_builder.build().unwrap(); + builder.set_vapid_signature(signature); + builder.set_payload(ContentEncoding::AesGcm, enc.as_bytes()); + let m = builder.build().unwrap(); + client.send(m).await.ok(); + } } } - } + }); Ok(()) } - pub async fn publish_update(&self, data: JsonValue) -> Result<()> { + pub async fn publish_update(self, data: JsonValue) -> Result<()> { let channel = self.channel.clone(); ClientboundNotification::MessageUpdate { id: self.id.clone(), data, } - .publish(channel) - .await - .ok(); + .publish(channel); + self.process_embed(); Ok(()) } + pub fn process_embed(&self) { + if let Content::Text(text) = &self.content { + let id = self.id.clone(); + let content = text.clone(); + let channel = self.channel.clone(); + async_std::task::spawn(async move { + if let Ok(embeds) = Embed::generate(content).await { + if let Ok(bson) = to_bson(&embeds) { + if let Ok(_) = get_collection("messages") + .update_one( + doc! { + "_id": &id + }, + doc! { + "$set": { + "embeds": bson + } + }, + None, + ) + .await { + ClientboundNotification::MessageUpdate { + id, + data: json!({ + "embeds": embeds + }), + } + .publish(channel); + } + } + } + }); + } + } + pub async fn delete(&self) -> Result<()> { if let Some(attachment) = &self.attachment { attachment.delete().await?; @@ -256,9 +287,7 @@ impl Message { ClientboundNotification::MessageDelete { id: self.id.clone(), } - .publish(channel) - .await - .ok(); + .publish(channel); if let Some(attachment) = &self.attachment { get_collection("attachments") diff --git a/src/database/entities/mod.rs b/src/database/entities/mod.rs index cdc9a43..90d99cb 100644 --- a/src/database/entities/mod.rs +++ b/src/database/entities/mod.rs @@ -1,9 +1,11 @@ +mod guild; mod autumn; +mod january; mod channel; -mod guild; mod message; mod user; +pub use january::*; pub use autumn::*; pub use channel::*; pub use guild::*; diff --git a/src/notifications/events.rs b/src/notifications/events.rs index 9ff4cf0..9366f16 100644 --- a/src/notifications/events.rs +++ b/src/notifications/events.rs @@ -107,9 +107,11 @@ pub enum ClientboundNotification { } impl ClientboundNotification { - pub async fn publish(self, topic: String) -> Result<(), String> { - prehandle_hook(&self); // ! TODO: this should be moved to pubsub - hive_pubsub::backend::mongo::publish(get_hive(), &topic, self).await + pub fn publish(self, topic: String) { + async_std::task::spawn(async move { + prehandle_hook(&self); // ! TODO: this should be moved to pubsub + hive_pubsub::backend::mongo::publish(get_hive(), &topic, self).await.ok(); + }); } } diff --git a/src/notifications/websocket.rs b/src/notifications/websocket.rs index 9df9b1b..cc0e06c 100644 --- a/src/notifications/websocket.rs +++ b/src/notifications/websocket.rs @@ -133,9 +133,7 @@ async fn accept(stream: TcpStream) { id: id.clone(), online: true, } - .publish(id) - .await - .ok(); + .publish(id); } } Err(_) => { @@ -173,9 +171,7 @@ async fn accept(stream: TcpStream) { id: channel.clone(), user, } - .publish(channel) - .await - .ok(); + .publish(channel); } else { send(ClientboundNotification::Error( WebSocketError::AlreadyAuthenticated, @@ -196,9 +192,7 @@ async fn accept(stream: TcpStream) { id: channel.clone(), user, } - .publish(channel) - .await - .ok(); + .publish(channel); } else { send(ClientboundNotification::Error( WebSocketError::AlreadyAuthenticated, @@ -238,9 +232,7 @@ async fn accept(stream: TcpStream) { id: id.clone(), online: false, } - .publish(id) - .await - .ok(); + .publish(id); } } diff --git a/src/routes/channels/delete_channel.rs b/src/routes/channels/delete_channel.rs index 28dd54b..9575ae6 100644 --- a/src/routes/channels/delete_channel.rs +++ b/src/routes/channels/delete_channel.rs @@ -95,9 +95,7 @@ pub async fn req(user: User, target: Ref) -> Result<()> { id: id.clone(), user: user.id.clone(), } - .publish(id.clone()) - .await - .ok(); + .publish(id.clone()); Message::create( "00000000000000000000000000".to_string(), diff --git a/src/routes/channels/edit_channel.rs b/src/routes/channels/edit_channel.rs index 5242ec9..d5f114e 100644 --- a/src/routes/channels/edit_channel.rs +++ b/src/routes/channels/edit_channel.rs @@ -101,9 +101,7 @@ pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> { data: json!(set), clear: data.remove } - .publish(id.clone()) - .await - .ok(); + .publish(id.clone()); if let Some(name) = data.name { Message::create( diff --git a/src/routes/channels/group_add_member.rs b/src/routes/channels/group_add_member.rs index f0c2670..f95090a 100644 --- a/src/routes/channels/group_add_member.rs +++ b/src/routes/channels/group_add_member.rs @@ -52,9 +52,7 @@ pub async fn req(user: User, target: Ref, member: Ref) -> Result<()> { id: id.clone(), user: member.id.clone(), } - .publish(id.clone()) - .await - .ok(); + .publish(id.clone()); Message::create( "00000000000000000000000000".to_string(), diff --git a/src/routes/channels/group_remove_member.rs b/src/routes/channels/group_remove_member.rs index 63c0f20..f26922f 100644 --- a/src/routes/channels/group_remove_member.rs +++ b/src/routes/channels/group_remove_member.rs @@ -49,9 +49,7 @@ pub async fn req(user: User, target: Ref, member: Ref) -> Result<()> { id: id.clone(), user: member.id.clone(), } - .publish(id.clone()) - .await - .ok(); + .publish(id.clone()); Message::create( "00000000000000000000000000".to_string(), diff --git a/src/routes/users/add_friend.rs b/src/routes/users/add_friend.rs index 4b1441b..ad0a494 100644 --- a/src/routes/users/add_friend.rs +++ b/src/routes/users/add_friend.rs @@ -74,21 +74,19 @@ pub async fn req(user: User, username: String) -> Result<JsonValue> { .from_override(&target_user, RelationshipStatus::Friend) .await?; - try_join!( - ClientboundNotification::UserRelationship { - id: user.id.clone(), - user: target_user, - status: RelationshipStatus::Friend - } - .publish(user.id.clone()), - ClientboundNotification::UserRelationship { - id: target_id.to_string(), - user, - status: RelationshipStatus::Friend - } - .publish(target_id.to_string()) - ) - .ok(); + ClientboundNotification::UserRelationship { + id: user.id.clone(), + user: target_user, + status: RelationshipStatus::Friend + } + .publish(user.id.clone()); + + ClientboundNotification::UserRelationship { + id: target_id.to_string(), + user, + status: RelationshipStatus::Friend + } + .publish(target_id.to_string()); Ok(json!({ "status": "Friend" })) } @@ -136,21 +134,20 @@ pub async fn req(user: User, username: String) -> Result<JsonValue> { let user = user .from_override(&target_user, RelationshipStatus::Incoming) .await?; - try_join!( - ClientboundNotification::UserRelationship { - id: user.id.clone(), - user: target_user, - status: RelationshipStatus::Outgoing - } - .publish(user.id.clone()), - ClientboundNotification::UserRelationship { - id: target_id.to_string(), - user, - status: RelationshipStatus::Incoming - } - .publish(target_id.to_string()) - ) - .ok(); + + ClientboundNotification::UserRelationship { + id: user.id.clone(), + user: target_user, + status: RelationshipStatus::Outgoing + } + .publish(user.id.clone()); + + ClientboundNotification::UserRelationship { + id: target_id.to_string(), + user, + status: RelationshipStatus::Incoming + } + .publish(target_id.to_string()); Ok(json!({ "status": "Outgoing" })) } diff --git a/src/routes/users/block_user.rs b/src/routes/users/block_user.rs index 10f69f2..6e06e0e 100644 --- a/src/routes/users/block_user.rs +++ b/src/routes/users/block_user.rs @@ -38,9 +38,7 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> { user: target, status: RelationshipStatus::Blocked, } - .publish(user.id.clone()) - .await - .ok(); + .publish(user.id.clone()); Ok(json!({ "status": "Blocked" })) } @@ -84,21 +82,19 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> { .await?; let target_id = target.id.clone(); - try_join!( - ClientboundNotification::UserRelationship { - id: user.id.clone(), - user: target, - status: RelationshipStatus::Blocked - } - .publish(user.id.clone()), - ClientboundNotification::UserRelationship { - id: target_id.clone(), - user, - status: RelationshipStatus::BlockedOther - } - .publish(target_id) - ) - .ok(); + ClientboundNotification::UserRelationship { + id: user.id.clone(), + user: target, + status: RelationshipStatus::Blocked + } + .publish(user.id.clone()); + + ClientboundNotification::UserRelationship { + id: target_id.clone(), + user, + status: RelationshipStatus::BlockedOther + } + .publish(target_id); Ok(json!({ "status": "Blocked" })) } @@ -146,21 +142,19 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> { .await?; let target_id = target.id.clone(); - try_join!( - ClientboundNotification::UserRelationship { - id: user.id.clone(), - user: target, - status: RelationshipStatus::Blocked - } - .publish(user.id.clone()), - ClientboundNotification::UserRelationship { - id: target_id.clone(), - user, - status: RelationshipStatus::BlockedOther - } - .publish(target_id) - ) - .ok(); + ClientboundNotification::UserRelationship { + id: user.id.clone(), + user: target, + status: RelationshipStatus::Blocked + } + .publish(user.id.clone()); + + ClientboundNotification::UserRelationship { + id: target_id.clone(), + user, + status: RelationshipStatus::BlockedOther + } + .publish(target_id); Ok(json!({ "status": "Blocked" })) } diff --git a/src/routes/users/change_username.rs b/src/routes/users/change_username.rs index 1daa11f..461818a 100644 --- a/src/routes/users/change_username.rs +++ b/src/routes/users/change_username.rs @@ -60,9 +60,7 @@ pub async fn req( data: json!(data.0), clear: None } - .publish(user.id.clone()) - .await - .ok(); + .publish(user.id.clone()); Ok(()) } diff --git a/src/routes/users/edit_user.rs b/src/routes/users/edit_user.rs index 5ce6b24..2f1197e 100644 --- a/src/routes/users/edit_user.rs +++ b/src/routes/users/edit_user.rs @@ -135,9 +135,7 @@ pub async fn req(user: User, data: Json<Data>, _ignore_id: String) -> Result<()> data: json!({ "status": status }), clear: None } - .publish(user.id.clone()) - .await - .ok(); + .publish(user.id.clone()); } if let Some(avatar) = attachment { @@ -146,9 +144,7 @@ pub async fn req(user: User, data: Json<Data>, _ignore_id: String) -> Result<()> data: json!({ "avatar": avatar }), clear: None } - .publish(user.id.clone()) - .await - .ok(); + .publish(user.id.clone()); } if let Some(clear) = data.remove { @@ -157,9 +153,7 @@ pub async fn req(user: User, data: Json<Data>, _ignore_id: String) -> Result<()> data: json!({}), clear: Some(clear) } - .publish(user.id.clone()) - .await - .ok(); + .publish(user.id.clone()); } if remove_avatar { diff --git a/src/routes/users/remove_friend.rs b/src/routes/users/remove_friend.rs index ec0176a..5426cf7 100644 --- a/src/routes/users/remove_friend.rs +++ b/src/routes/users/remove_friend.rs @@ -53,21 +53,19 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> { .await?; let target_id = target.id.clone(); - try_join!( - ClientboundNotification::UserRelationship { - id: user.id.clone(), - user: target, - status: RelationshipStatus::None - } - .publish(user.id.clone()), - ClientboundNotification::UserRelationship { - id: target_id.clone(), - user, - status: RelationshipStatus::None - } - .publish(target_id) - ) - .ok(); + ClientboundNotification::UserRelationship { + id: user.id.clone(), + user: target, + status: RelationshipStatus::None + } + .publish(user.id.clone()); + + ClientboundNotification::UserRelationship { + id: target_id.clone(), + user, + status: RelationshipStatus::None + } + .publish(target_id); Ok(json!({ "status": "None" })) } diff --git a/src/routes/users/unblock_user.rs b/src/routes/users/unblock_user.rs index 48db2e7..4e2e59c 100644 --- a/src/routes/users/unblock_user.rs +++ b/src/routes/users/unblock_user.rs @@ -40,9 +40,7 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> { user: target, status: RelationshipStatus::BlockedOther, } - .publish(user.id.clone()) - .await - .ok(); + .publish(user.id.clone()); Ok(json!({ "status": "BlockedOther" })) } @@ -84,21 +82,19 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> { .await?; let target_id = target.id.clone(); - try_join!( - ClientboundNotification::UserRelationship { - id: user.id.clone(), - user: target, - status: RelationshipStatus::None - } - .publish(user.id.clone()), - ClientboundNotification::UserRelationship { - id: target_id.clone(), - user: user, - status: RelationshipStatus::None - } - .publish(target_id) - ) - .ok(); + ClientboundNotification::UserRelationship { + id: user.id.clone(), + user: target, + status: RelationshipStatus::None + } + .publish(user.id.clone()); + + ClientboundNotification::UserRelationship { + id: target_id.clone(), + user: user, + status: RelationshipStatus::None + } + .publish(target_id); Ok(json!({ "status": "None" })) } diff --git a/src/util/variables.rs b/src/util/variables.rs index 241d2ab..1bd71e9 100644 --- a/src/util/variables.rs +++ b/src/util/variables.rs @@ -18,6 +18,8 @@ lazy_static! { pub static ref AUTUMN_URL: String = env::var("AUTUMN_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string()); + pub static ref JANUARY_URL: String = + env::var("JANUARY_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string()); pub static ref VOSO_URL: String = env::var("VOSO_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string()); pub static ref VOSO_WS_HOST: String = -- GitLab