diff --git a/src/database/entities/message.rs b/src/database/entities/message.rs index 7191f1644813796a9991d9ddd9e75750338b3336..1d48c2d2dbae5ff077e4adf46a19229ab3f2f9d1 100644 --- a/src/database/entities/message.rs +++ b/src/database/entities/message.rs @@ -20,42 +20,29 @@ use web_push::{ #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "type")] pub enum MessageEmbed { - Dummy + Dummy, } #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "type")] pub enum SystemMessage { #[serde(rename = "text")] - Text { - content: String - }, + Text { content: String }, #[serde(rename = "user_added")] - UserAdded { - id: String, - by: String - }, + UserAdded { id: String, by: String }, #[serde(rename = "user_remove")] - UserRemove { - id: String, - by: String - }, + UserRemove { id: String, by: String }, #[serde(rename = "user_left")] - UserLeft { - id: String - }, + UserLeft { id: String }, #[serde(rename = "channel_renamed")] - ChannelRenamed { - name: String, - by: String - }, + ChannelRenamed { name: String, by: String }, } #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] pub enum Content { Text(String), - SystemMessage(SystemMessage) + SystemMessage(SystemMessage), } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -87,7 +74,7 @@ impl Message { content, attachment: None, edited: None, - embeds: None + embeds: None, } } @@ -174,7 +161,7 @@ impl Message { } // Fetch their corresponding sessions. - let mut cursor = get_collection("accounts") + if let Ok(mut cursor) = get_collection("accounts") .find( doc! { "_id": { @@ -189,41 +176,43 @@ impl Message { .build(), ) .await - .unwrap(); // !FIXME + { + 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(); - 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(); + } } } @@ -255,7 +244,7 @@ impl Message { "deleted": true } }, - None + None, ) .await .map_err(|_| Error::DatabaseError { diff --git a/src/database/entities/user.rs b/src/database/entities/user.rs index 16f9ecd023e9422cfbf0376d10791748c0163a46..51580699ff371f9213fb7522b7cffa88465cb889 100644 --- a/src/database/entities/user.rs +++ b/src/database/entities/user.rs @@ -1,12 +1,12 @@ use mongodb::bson::doc; -use serde::{Deserialize, Serialize}; use mongodb::options::{Collation, FindOneOptions}; +use serde::{Deserialize, Serialize}; +use crate::database::permissions::user::UserPermissions; use crate::database::*; -use validator::Validate; -use crate::util::result::{Error, Result}; use crate::notifications::websocket::is_online; -use crate::database::permissions::user::UserPermissions; +use crate::util::result::{Error, Result}; +use validator::Validate; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub enum RelationshipStatus { @@ -37,14 +37,14 @@ pub enum Badge { pub struct UserStatus { #[validate(length(min = 1, max = 128))] #[serde(skip_serializing_if = "Option::is_none")] - text: Option<String> + text: Option<String>, } #[derive(Validate, Serialize, Deserialize, Debug)] pub struct UserProfile { #[validate(length(min = 1, max = 2000))] #[serde(skip_serializing_if = "Option::is_none")] - content: Option<String> + content: Option<String>, } #[derive(Serialize, Deserialize, Debug)] @@ -54,7 +54,7 @@ pub struct User { pub username: String, #[serde(skip_serializing_if = "Option::is_none")] pub relations: Option<Vec<Relationship>>, - + #[serde(skip_serializing_if = "Option::is_none")] pub badges: Option<i32>, #[serde(skip_serializing_if = "Option::is_none")] @@ -101,7 +101,7 @@ impl User { /// Utility function for checking claimed usernames. pub async fn is_username_taken(username: &str) -> Result<bool> { if username == "revolt" && username == "admin" { - return Ok(true) + return Ok(true); } if get_collection("users") diff --git a/src/database/permissions/channel.rs b/src/database/permissions/channel.rs index a1632476d5189991a35f947fe692e23943af3e22..c82fb2cee32659e9437481b654a3b1002fefd567 100644 --- a/src/database/permissions/channel.rs +++ b/src/database/permissions/channel.rs @@ -56,7 +56,9 @@ impl<'a> PermissionCalculator<'a> { let perms = self.for_user(recipient).await?; if perms.get_send_message() { - return Ok(ChannelPermission::View + ChannelPermission::SendMessage + ChannelPermission::VoiceCall); + return Ok(ChannelPermission::View + + ChannelPermission::SendMessage + + ChannelPermission::VoiceCall); } return Ok(ChannelPermission::View as u32); @@ -71,7 +73,10 @@ impl<'a> PermissionCalculator<'a> { .find(|x| *x == &self.perspective.id) .is_some() { - Ok(ChannelPermission::View + ChannelPermission::SendMessage + ChannelPermission::ManageChannel + ChannelPermission::VoiceCall) + Ok(ChannelPermission::View + + ChannelPermission::SendMessage + + ChannelPermission::ManageChannel + + ChannelPermission::VoiceCall) } else { Ok(0) } diff --git a/src/notifications/events.rs b/src/notifications/events.rs index d097fa7d2455354e18b5f51ffac3caf7e19460ec..4e92bdb9113f6fc8e196bdffdcecc94a94f5ddd3 100644 --- a/src/notifications/events.rs +++ b/src/notifications/events.rs @@ -26,12 +26,8 @@ pub enum WebSocketError { #[serde(tag = "type")] pub enum ServerboundNotification { Authenticate(Session), - BeginTyping { - channel: String - }, - EndTyping { - channel: String - } + BeginTyping { channel: String }, + EndTyping { channel: String }, } #[derive(Serialize, Deserialize, Debug)] @@ -71,11 +67,11 @@ pub enum ClientboundNotification { }, ChannelStartTyping { id: String, - user: String + user: String, }, ChannelStopTyping { id: String, - user: String + user: String, }, UserUpdate { diff --git a/src/notifications/websocket.rs b/src/notifications/websocket.rs index 121c58e63122a065a7ad950d8dd747452313953a..286067281324835c35804c907d7439b853b75e66 100644 --- a/src/notifications/websocket.rs +++ b/src/notifications/websocket.rs @@ -171,7 +171,7 @@ async fn accept(stream: TcpStream) { ClientboundNotification::ChannelStartTyping { id: channel.clone(), - user + user, } .publish(channel) .await @@ -194,7 +194,7 @@ async fn accept(stream: TcpStream) { ClientboundNotification::ChannelStopTyping { id: channel.clone(), - user + user, } .publish(channel) .await diff --git a/src/routes/channels/delete_channel.rs b/src/routes/channels/delete_channel.rs index f7fa08b84d3fbacba5dcf12f63f2ca50dab0f7c1..28dd54b8fc0681e04f697db66b37180411654627 100644 --- a/src/routes/channels/delete_channel.rs +++ b/src/routes/channels/delete_channel.rs @@ -102,7 +102,7 @@ pub async fn req(user: User, target: Ref) -> Result<()> { Message::create( "00000000000000000000000000".to_string(), id.clone(), - Content::SystemMessage(SystemMessage::UserLeft { id: user.id }) + Content::SystemMessage(SystemMessage::UserLeft { id: user.id }), ) .publish(&target) .await diff --git a/src/routes/channels/edit_channel.rs b/src/routes/channels/edit_channel.rs index 8c8e86043ba1740d17133211036442a08124d7a9..ebd573fe8148e31737547ce25b3e7a4fb8caa721 100644 --- a/src/routes/channels/edit_channel.rs +++ b/src/routes/channels/edit_channel.rs @@ -1,11 +1,11 @@ use crate::database::*; -use crate::util::result::{Error, Result}; use crate::notifications::events::ClientboundNotification; +use crate::util::result::{Error, Result}; use mongodb::bson::{doc, to_document}; -use validator::Validate; use rocket_contrib::json::Json; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; +use validator::Validate; #[derive(Validate, Serialize, Deserialize)] pub struct Data { @@ -21,9 +21,9 @@ pub struct Data { pub async fn req(user: User, target: Ref, info: Json<Data>) -> Result<()> { info.validate() .map_err(|error| Error::FailedValidation { error })?; - + if info.name.is_none() && info.description.is_none() { - return Ok(()) + return Ok(()); } let target = target.fetch_channel().await?; @@ -31,7 +31,7 @@ pub async fn req(user: User, target: Ref, info: Json<Data>) -> Result<()> { .with_channel(&target) .for_channel() .await?; - + if !perm.get_manage_channel() { Err(Error::MissingPermission)? } @@ -49,7 +49,7 @@ pub async fn req(user: User, target: Ref, info: Json<Data>) -> Result<()> { ClientboundNotification::ChannelUpdate { id: id.clone(), - data: json!(info.0) + data: json!(info.0), } .publish(id.clone()) .await @@ -59,7 +59,10 @@ pub async fn req(user: User, target: Ref, info: Json<Data>) -> Result<()> { Message::create( "00000000000000000000000000".to_string(), id.clone(), - Content::SystemMessage(SystemMessage::ChannelRenamed { name: name.clone(), by: user.id }) + Content::SystemMessage(SystemMessage::ChannelRenamed { + name: name.clone(), + by: user.id, + }), ) .publish(&target) .await @@ -68,6 +71,6 @@ pub async fn req(user: User, target: Ref, info: Json<Data>) -> Result<()> { Ok(()) } - _ => Err(Error::InvalidOperation) + _ => Err(Error::InvalidOperation), } } diff --git a/src/routes/channels/group_add_member.rs b/src/routes/channels/group_add_member.rs index 8fc2d8bc7b0775705bcb4b06bba63e91bfc6443d..f0c26706bd53d843c3b3649822d18acc685a4c9f 100644 --- a/src/routes/channels/group_add_member.rs +++ b/src/routes/channels/group_add_member.rs @@ -59,7 +59,10 @@ pub async fn req(user: User, target: Ref, member: Ref) -> Result<()> { Message::create( "00000000000000000000000000".to_string(), id.clone(), - Content::SystemMessage(SystemMessage::UserAdded { id: member.id, by: user.id }) + Content::SystemMessage(SystemMessage::UserAdded { + id: member.id, + by: user.id, + }), ) .publish(&channel) .await diff --git a/src/routes/channels/group_remove_member.rs b/src/routes/channels/group_remove_member.rs index 9cc25417258ea8263e2cbb83761dec60c2d92716..63c0f2062fa2316498b3378163e54d18431dc7bd 100644 --- a/src/routes/channels/group_remove_member.rs +++ b/src/routes/channels/group_remove_member.rs @@ -56,7 +56,10 @@ pub async fn req(user: User, target: Ref, member: Ref) -> Result<()> { Message::create( "00000000000000000000000000".to_string(), id.clone(), - Content::SystemMessage(SystemMessage::UserRemove { id: member.id, by: user.id }) + Content::SystemMessage(SystemMessage::UserRemove { + id: member.id, + by: user.id, + }), ) .publish(&channel) .await diff --git a/src/routes/channels/join_call.rs b/src/routes/channels/join_call.rs index fc3254f08437f2da34f392c4e8247c4204145e8a..02d0db842c2500378096c04469a9a5cf9c7f1c95 100644 --- a/src/routes/channels/join_call.rs +++ b/src/routes/channels/join_call.rs @@ -1,19 +1,19 @@ use crate::database::*; use crate::util::result::{Error, Result}; -use crate::util::variables::{USE_VOSO, VOSO_URL, VOSO_MANAGE_TOKEN}; +use crate::util::variables::{USE_VOSO, VOSO_MANAGE_TOKEN, VOSO_URL}; -use serde::{Serialize, Deserialize}; use rocket_contrib::json::JsonValue; +use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] struct CreateUserResponse { - token: String + token: String, } #[post("/<target>/join_call")] pub async fn req(user: User, target: Ref) -> Result<JsonValue> { if !*USE_VOSO { - return Err(Error::VosoUnavailable) + return Err(Error::VosoUnavailable); } let target = target.fetch_channel().await?; @@ -21,9 +21,9 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> { .with_channel(&target) .for_channel() .await?; - + if !perm.get_voice_call() { - return Err(Error::MissingPermission) + return Err(Error::MissingPermission); } // To join a call: @@ -32,39 +32,51 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> { let client = reqwest::Client::new(); let result = client .get(&format!("{}/room/{}", *VOSO_URL, target.id())) - .header(reqwest::header::AUTHORIZATION, VOSO_MANAGE_TOKEN.to_string()) + .header( + reqwest::header::AUTHORIZATION, + VOSO_MANAGE_TOKEN.to_string(), + ) .send() .await; - + match result { Err(_) => return Err(Error::VosoUnavailable), - Ok(result) => { - match result.status() { - reqwest::StatusCode::OK => (), - reqwest::StatusCode::NOT_FOUND => { - if let Err(_) = client - .post(&format!("{}/room/{}", *VOSO_URL, target.id())) - .header(reqwest::header::AUTHORIZATION, VOSO_MANAGE_TOKEN.to_string()) - .send() - .await { - return Err(Error::VosoUnavailable) - } - }, - _ => return Err(Error::VosoUnavailable) + Ok(result) => match result.status() { + reqwest::StatusCode::OK => (), + reqwest::StatusCode::NOT_FOUND => { + if let Err(_) = client + .post(&format!("{}/room/{}", *VOSO_URL, target.id())) + .header( + reqwest::header::AUTHORIZATION, + VOSO_MANAGE_TOKEN.to_string(), + ) + .send() + .await + { + return Err(Error::VosoUnavailable); + } } - } + _ => return Err(Error::VosoUnavailable), + }, } // Then create a user for the room. if let Ok(response) = client - .post(&format!("{}/room/{}/user/{}", *VOSO_URL, target.id(), user.id)) - .header(reqwest::header::AUTHORIZATION, VOSO_MANAGE_TOKEN.to_string()) + .post(&format!( + "{}/room/{}/user/{}", + *VOSO_URL, + target.id(), + user.id + )) + .header( + reqwest::header::AUTHORIZATION, + VOSO_MANAGE_TOKEN.to_string(), + ) .send() - .await { - let res: CreateUserResponse = response.json() - .await - .map_err(|_| Error::InvalidOperation)?; - + .await + { + let res: CreateUserResponse = response.json().await.map_err(|_| Error::InvalidOperation)?; + Ok(json!(res)) } else { Err(Error::VosoUnavailable) diff --git a/src/routes/channels/message_send.rs b/src/routes/channels/message_send.rs index 626fba918dc95b3d5604c04b57c73b25b54974dd..8a4d30b7a72e5a5e6a5724f1e81f76eeedf2ce74 100644 --- a/src/routes/channels/message_send.rs +++ b/src/routes/channels/message_send.rs @@ -26,7 +26,7 @@ pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonVal message .validate() .map_err(|error| Error::FailedValidation { error })?; - + if message.content.len() == 0 && message.attachment.is_none() { return Err(Error::EmptyMessage); } @@ -119,7 +119,7 @@ pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonVal attachment, nonce: Some(message.nonce.clone()), edited: None, - embeds: None + embeds: None, }; msg.clone().publish(&target).await?; diff --git a/src/routes/onboard/complete.rs b/src/routes/onboard/complete.rs index 68583efe2b789cd996fd73cd9fd9c18795801a21..5ebb1f15ca87947a52556d21379791f19adf24b4 100644 --- a/src/routes/onboard/complete.rs +++ b/src/routes/onboard/complete.rs @@ -28,21 +28,22 @@ pub async fn req(session: Session, user: Option<User>, data: Json<Data>) -> Resu .map_err(|error| Error::FailedValidation { error })?; if User::is_username_taken(&data.username).await? { - return Err(Error::UsernameTaken) + return Err(Error::UsernameTaken); } - get_collection("users").insert_one( - doc! { - "_id": session.user_id, - "username": &data.username - }, - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "insert_one", - with: "user", - })?; + get_collection("users") + .insert_one( + doc! { + "_id": session.user_id, + "username": &data.username + }, + None, + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "insert_one", + with: "user", + })?; Ok(()) } diff --git a/src/routes/root.rs b/src/routes/root.rs index 41375c1524286ff644bd07019bf0b7b91f9fc08a..96282d398962dbc753351b4cbd6d0f83d8956671 100644 --- a/src/routes/root.rs +++ b/src/routes/root.rs @@ -1,6 +1,6 @@ use crate::util::variables::{ - AUTUMN_URL, DISABLE_REGISTRATION, EXTERNAL_WS_URL, HCAPTCHA_SITEKEY, INVITE_ONLY, USE_AUTUMN, APP_URL, - USE_EMAIL, USE_HCAPTCHA, VAPID_PUBLIC_KEY, USE_VOSO, VOSO_URL, VOSO_WS_HOST + APP_URL, AUTUMN_URL, DISABLE_REGISTRATION, EXTERNAL_WS_URL, HCAPTCHA_SITEKEY, INVITE_ONLY, + USE_AUTUMN, USE_EMAIL, USE_HCAPTCHA, USE_VOSO, VAPID_PUBLIC_KEY, VOSO_URL, VOSO_WS_HOST, }; use mongodb::bson::doc; diff --git a/src/routes/users/change_username.rs b/src/routes/users/change_username.rs index ede137612d9b7a99c3eca62264c249291bcdb642..077709f3c65dade811aed454a52fc9c1a3be9751 100644 --- a/src/routes/users/change_username.rs +++ b/src/routes/users/change_username.rs @@ -1,14 +1,14 @@ use crate::database::*; -use crate::util::result::{Error, Result}; use crate::notifications::events::ClientboundNotification; +use crate::util::result::{Error, Result}; +use mongodb::bson::doc; use rauth::auth::{Auth, Session}; use regex::Regex; -use mongodb::bson::doc; use rocket::State; -use validator::Validate; use rocket_contrib::json::Json; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; +use validator::Validate; // ! FIXME: should be global somewhere; maybe use config(?) lazy_static! { @@ -24,10 +24,15 @@ pub struct Data { } #[patch("/username", data = "<data>")] -pub async fn req(auth: State<'_, Auth>, session: Session, user: User, data: Json<Data>) -> Result<()> { +pub async fn req( + auth: State<'_, Auth>, + session: Session, + user: User, + data: Json<Data>, +) -> Result<()> { data.validate() .map_err(|error| Error::FailedValidation { error })?; - + auth.verify_password(&session, data.password.clone()) .await .map_err(|_| Error::InvalidCredentials)?; @@ -35,24 +40,23 @@ pub async fn req(auth: State<'_, Auth>, session: Session, user: User, data: Json let mut set = doc! {}; if let Some(username) = &data.username { if User::is_username_taken(&username).await? { - return Err(Error::UsernameTaken) + return Err(Error::UsernameTaken); } set.insert("username", username.clone()); } - + get_collection("users") - .update_one( - doc! { "_id": &user.id }, - doc! { "$set": set }, - None - ) - .await - .map_err(|_| Error::DatabaseError { operation: "update_one", with: "user" })?; + .update_one(doc! { "_id": &user.id }, doc! { "$set": set }, None) + .await + .map_err(|_| Error::DatabaseError { + operation: "update_one", + with: "user", + })?; ClientboundNotification::UserUpdate { id: user.id.clone(), - data: json!(data.0) + data: json!(data.0), } .publish(user.id.clone()) .await diff --git a/src/routes/users/edit_user.rs b/src/routes/users/edit_user.rs index 468748aeca4574703e18fc35354a6ef94815aead..e240c3c4a6e183b3148830eec5ae759b28c13da0 100644 --- a/src/routes/users/edit_user.rs +++ b/src/routes/users/edit_user.rs @@ -1,25 +1,25 @@ use crate::database::*; -use crate::util::result::{Error, Result}; use crate::notifications::events::ClientboundNotification; +use crate::util::result::{Error, Result}; use mongodb::bson::{doc, to_document}; -use validator::Validate; use rocket_contrib::json::Json; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; +use validator::Validate; #[derive(Validate, Serialize, Deserialize)] pub struct Data { #[serde(skip_serializing_if = "Option::is_none")] status: Option<UserStatus>, #[serde(skip_serializing_if = "Option::is_none")] - profile: Option<UserProfile> + profile: Option<UserProfile>, } #[patch("/", data = "<data>")] pub async fn req(user: User, data: Json<Data>) -> Result<()> { data.validate() .map_err(|error| Error::FailedValidation { error })?; - + get_collection("users") .update_one( doc! { "_id": &user.id }, @@ -31,7 +31,7 @@ pub async fn req(user: User, data: Json<Data>) -> Result<()> { ClientboundNotification::UserUpdate { id: user.id.clone(), - data: json!(data.0) + data: json!(data.0), } .publish(user.id.clone()) .await diff --git a/src/routes/users/fetch_profile.rs b/src/routes/users/fetch_profile.rs index 7e2b6b91dec04681a7904d91d1a05be61a9a3a2f..089f88e6728898beed057fd9e98e24fce0b9e16d 100644 --- a/src/routes/users/fetch_profile.rs +++ b/src/routes/users/fetch_profile.rs @@ -11,7 +11,7 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> { .with_user(&target) .for_user_given() .await?; - + if !perm.get_view_profile() { Err(Error::MissingPermission)? } diff --git a/src/util/variables.rs b/src/util/variables.rs index f8f7d3f827711db3ca4d4c968a6eb906da480e07..241d2aba132b7db90b0188b59f6b542b4fbab46d 100644 --- a/src/util/variables.rs +++ b/src/util/variables.rs @@ -15,7 +15,7 @@ lazy_static! { env::var("REVOLT_APP_URL").expect("Missing REVOLT_APP_URL environment variable."); pub static ref EXTERNAL_WS_URL: String = env::var("REVOLT_EXTERNAL_WS_URL").expect("Missing REVOLT_EXTERNAL_WS_URL environment variable."); - + pub static ref AUTUMN_URL: String = env::var("AUTUMN_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string()); pub static ref VOSO_URL: String = @@ -24,7 +24,7 @@ lazy_static! { env::var("VOSO_WS_HOST").unwrap_or_else(|_| "wss://example.com".to_string()); pub static ref VOSO_MANAGE_TOKEN: String = env::var("VOSO_MANAGE_TOKEN").unwrap_or_else(|_| "0".to_string()); - + pub static ref HCAPTCHA_KEY: String = env::var("REVOLT_HCAPTCHA_KEY").unwrap_or_else(|_| "0x0000000000000000000000000000000000000000".to_string()); pub static ref HCAPTCHA_SITEKEY: String =