Compare commits

...
12 Commits
26 changed files with 226 additions and 138 deletions

No files matched your search

Generated
+12 -12
View File
@@ -5580,7 +5580,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-autumn" name = "revolt-autumn"
version = "0.7.18" version = "0.7.19"
dependencies = [ dependencies = [
"axum", "axum",
"axum-macros", "axum-macros",
@@ -5617,7 +5617,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-bonfire" name = "revolt-bonfire"
version = "0.7.18" version = "0.7.19"
dependencies = [ dependencies = [
"async-channel 2.3.1", "async-channel 2.3.1",
"async-std", "async-std",
@@ -5647,7 +5647,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-config" name = "revolt-config"
version = "0.7.18" version = "0.7.19"
dependencies = [ dependencies = [
"async-std", "async-std",
"cached", "cached",
@@ -5664,7 +5664,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-database" name = "revolt-database"
version = "0.7.18" version = "0.7.19"
dependencies = [ dependencies = [
"async-lock 2.8.0", "async-lock 2.8.0",
"async-recursion", "async-recursion",
@@ -5712,7 +5712,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-delta" name = "revolt-delta"
version = "0.7.18" version = "0.7.19"
dependencies = [ dependencies = [
"async-channel 1.6.1", "async-channel 1.6.1",
"async-std", "async-std",
@@ -5759,7 +5759,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-files" name = "revolt-files"
version = "0.7.18" version = "0.7.19"
dependencies = [ dependencies = [
"aes-gcm 0.10.3", "aes-gcm 0.10.3",
"aws-config", "aws-config",
@@ -5782,7 +5782,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-january" name = "revolt-january"
version = "0.7.18" version = "0.7.19"
dependencies = [ dependencies = [
"async-recursion", "async-recursion",
"axum", "axum",
@@ -5810,7 +5810,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-models" name = "revolt-models"
version = "0.7.18" version = "0.7.19"
dependencies = [ dependencies = [
"indexmap 1.9.3", "indexmap 1.9.3",
"iso8601-timestamp 0.2.11", "iso8601-timestamp 0.2.11",
@@ -5828,7 +5828,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-nodejs-bindings" name = "revolt-nodejs-bindings"
version = "0.7.18" version = "0.7.19"
dependencies = [ dependencies = [
"async-std", "async-std",
"neon", "neon",
@@ -5841,7 +5841,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-permissions" name = "revolt-permissions"
version = "0.7.18" version = "0.7.19"
dependencies = [ dependencies = [
"async-std", "async-std",
"async-trait", "async-trait",
@@ -5856,7 +5856,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-presence" name = "revolt-presence"
version = "0.7.18" version = "0.7.19"
dependencies = [ dependencies = [
"async-std", "async-std",
"log", "log",
@@ -5867,7 +5867,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-result" name = "revolt-result"
version = "0.7.18" version = "0.7.19"
dependencies = [ dependencies = [
"axum", "axum",
"revolt_okapi", "revolt_okapi",
+4 -4
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-nodejs-bindings" name = "revolt-nodejs-bindings"
version = "0.7.18" version = "0.7.19"
description = "Node.js bindings for the Revolt software" description = "Node.js bindings for the Revolt software"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
license = "MIT" license = "MIT"
@@ -20,6 +20,6 @@ serde = { version = "1", features = ["derive"] }
async-std = "1.12.0" async-std = "1.12.0"
revolt-config = { version = "0.7.18", path = "../../core/config" } revolt-config = { version = "0.7.19", path = "../../core/config" }
revolt-result = { version = "0.7.18", path = "../../core/result" } revolt-result = { version = "0.7.19", path = "../../core/result" }
revolt-database = { version = "0.7.18", path = "../../core/database" } revolt-database = { version = "0.7.19", path = "../../core/database" }
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-bonfire" name = "revolt-bonfire"
version = "0.7.18" version = "0.7.19"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
edition = "2021" edition = "2021"
@@ -41,7 +41,7 @@ revolt-result = { path = "../core/result" }
revolt-models = { path = "../core/models" } revolt-models = { path = "../core/models" }
revolt-config = { path = "../core/config" } revolt-config = { path = "../core/config" }
revolt-database = { path = "../core/database" } revolt-database = { path = "../core/database" }
revolt-permissions = { version = "0.7.18", path = "../core/permissions" } revolt-permissions = { version = "0.7.19", path = "../core/permissions" }
revolt-presence = { path = "../core/presence", features = ["redis-is-patched"] } revolt-presence = { path = "../core/presence", features = ["redis-is-patched"] }
# redis # redis
+4 -6
View File
@@ -301,25 +301,23 @@ async fn listener(
PayloadType::Json => message PayloadType::Json => message
.value .value
.as_str() .as_str()
.and_then(|s| serde_json::from_str::<EventV1>(s.as_ref()).ok()), .and_then(|s| report_internal_error!(serde_json::from_str::<EventV1>(s.as_ref())).ok()),
PayloadType::Msgpack => message PayloadType::Msgpack => message
.value .value
.as_bytes() .as_bytes()
.and_then(|b| rmp_serde::from_slice::<EventV1>(b).ok()), .and_then(|b| report_internal_error!(rmp_serde::from_slice::<EventV1>(b)).ok()),
PayloadType::Bincode => message PayloadType::Bincode => message
.value .value
.as_bytes() .as_bytes()
.and_then(|b| bincode::deserialize::<EventV1>(b).ok()), .and_then(|b| report_internal_error!(bincode::deserialize::<EventV1>(b)).ok()),
}; };
let Some(mut event) = event else { let Some(mut event) = event else {
let err = format!( let err = format!(
"Failed to deserialise an event for {}! Introspection: `{:?}`", "Failed to deserialise event for {}: `{:?}`",
message.channel, message.channel,
message message
.value .value
.as_string()
.map(|x| x.chars().take(32).collect::<String>())
); );
error!("{}", err); error!("{}", err);
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-config" name = "revolt-config"
version = "0.7.18" version = "0.7.19"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
@@ -35,4 +35,4 @@ pretty_env_logger = "0.4.0"
sentry = "0.31.5" sentry = "0.31.5"
# Core # Core
revolt-result = { version = "0.7.18", path = "../result", optional = true } revolt-result = { version = "0.7.19", path = "../result", optional = true }
+6 -6
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-database" name = "revolt-database"
version = "0.7.18" version = "0.7.19"
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>"]
@@ -24,15 +24,15 @@ default = ["mongodb", "async-std-runtime", "tasks"]
[dependencies] [dependencies]
# Core # Core
revolt-config = { version = "0.7.18", path = "../config", features = [ revolt-config = { version = "0.7.19", path = "../config", features = [
"report-macros", "report-macros",
] } ] }
revolt-result = { version = "0.7.18", path = "../result" } revolt-result = { version = "0.7.19", path = "../result" }
revolt-models = { version = "0.7.18", path = "../models", features = [ revolt-models = { version = "0.7.19", path = "../models", features = [
"validator", "validator",
] } ] }
revolt-presence = { version = "0.7.18", path = "../presence" } revolt-presence = { version = "0.7.19", path = "../presence" }
revolt-permissions = { version = "0.7.18", path = "../permissions", features = [ revolt-permissions = { version = "0.7.19", path = "../permissions", features = [
"serde", "serde",
"bson", "bson",
] } ] }
+10 -1
View File
@@ -2,7 +2,10 @@ use authifier::AuthifierEvent;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use revolt_models::v0::{ use revolt_models::v0::{
AppendMessage, Channel, ChannelUnread, Emoji, FieldsChannel, FieldsMember, FieldsMessage, FieldsRole, FieldsServer, FieldsUser, FieldsWebhook, Member, MemberCompositeKey, Message, PartialChannel, PartialMember, PartialMessage, PartialRole, PartialServer, PartialUser, PartialWebhook, RemovalIntention, Report, Server, User, UserSettings, Webhook AppendMessage, Channel, ChannelUnread, Emoji, FieldsChannel, FieldsMember, FieldsMessage,
FieldsRole, FieldsServer, FieldsUser, FieldsWebhook, Member, MemberCompositeKey, Message,
PartialChannel, PartialMember, PartialMessage, PartialRole, PartialServer, PartialUser,
PartialWebhook, RemovalIntention, Report, Server, User, UserSettings, Webhook,
}; };
use revolt_result::Error; use revolt_result::Error;
@@ -89,6 +92,7 @@ pub enum EventV1 {
id: String, id: String,
channel: String, channel: String,
data: PartialMessage, data: PartialMessage,
#[serde(default)]
clear: Vec<FieldsMessage>, clear: Vec<FieldsMessage>,
}, },
@@ -140,6 +144,7 @@ pub enum EventV1 {
ServerUpdate { ServerUpdate {
id: String, id: String,
data: PartialServer, data: PartialServer,
#[serde(default)]
clear: Vec<FieldsServer>, clear: Vec<FieldsServer>,
}, },
@@ -150,6 +155,7 @@ pub enum EventV1 {
ServerMemberUpdate { ServerMemberUpdate {
id: MemberCompositeKey, id: MemberCompositeKey,
data: PartialMember, data: PartialMember,
#[serde(default)]
clear: Vec<FieldsMember>, clear: Vec<FieldsMember>,
}, },
@@ -168,6 +174,7 @@ pub enum EventV1 {
id: String, id: String,
role_id: String, role_id: String,
data: PartialRole, data: PartialRole,
#[serde(default)]
clear: Vec<FieldsRole>, clear: Vec<FieldsRole>,
}, },
@@ -178,6 +185,7 @@ pub enum EventV1 {
UserUpdate { UserUpdate {
id: String, id: String,
data: PartialUser, data: PartialUser,
#[serde(default)]
clear: Vec<FieldsUser>, clear: Vec<FieldsUser>,
event_id: Option<String>, event_id: Option<String>,
}, },
@@ -212,6 +220,7 @@ pub enum EventV1 {
ChannelUpdate { ChannelUpdate {
id: String, id: String,
data: PartialChannel, data: PartialChannel,
#[serde(default)]
clear: Vec<FieldsChannel>, clear: Vec<FieldsChannel>,
}, },
@@ -1,4 +1,4 @@
use std::collections::HashSet; use std::{collections::HashSet, hash::RandomState};
use indexmap::{IndexMap, IndexSet}; use indexmap::{IndexMap, IndexSet};
use iso8601_timestamp::Timestamp; use iso8601_timestamp::Timestamp;
@@ -338,7 +338,34 @@ impl Message {
} }
if !mentions.is_empty() { if !mentions.is_empty() {
message.mentions.replace(mentions.into_iter().collect()); // FIXME: temp fix to stop spam attacks
match channel {
Channel::DirectMessage { ref recipients, .. }
| Channel::Group { ref recipients, .. } => {
let recipients_hash: HashSet<&String, RandomState> =
HashSet::from_iter(recipients.iter());
mentions.retain(|m| recipients_hash.contains(m));
}
Channel::TextChannel { ref server, .. }
| Channel::VoiceChannel { ref server, .. } => {
let mentions_vec = Vec::from_iter(mentions.iter().cloned());
let valid_members = db.fetch_members(server.as_str(), &mentions_vec[..]).await;
if let Ok(valid_members) = valid_members {
let valid_ids: HashSet<String, RandomState> = HashSet::from_iter(
valid_members.iter().map(|member| member.id.user.clone()),
);
mentions.retain(|m| valid_ids.contains(m));
} else {
revolt_config::capture_error(&valid_members.unwrap_err());
}
}
Channel::SavedMessages { .. } => mentions.clear(),
}
if !mentions.is_empty() {
message.mentions.replace(mentions.into_iter().collect());
}
} }
if !replies.is_empty() { if !replies.is_empty() {
+3 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-files" name = "revolt-files"
version = "0.7.18" version = "0.7.19"
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>"]
@@ -20,10 +20,10 @@ typenum = "1.17.0"
aws-config = "1.5.5" aws-config = "1.5.5"
aws-sdk-s3 = { version = "1.46.0", features = ["behavior-version-latest"] } aws-sdk-s3 = { version = "1.46.0", features = ["behavior-version-latest"] }
revolt-config = { version = "0.7.18", path = "../config", features = [ revolt-config = { version = "0.7.19", path = "../config", features = [
"report-macros", "report-macros",
] } ] }
revolt-result = { version = "0.7.18", path = "../result" } revolt-result = { version = "0.7.19", path = "../result" }
# image processing # image processing
jxl-oxide = "0.8.1" jxl-oxide = "0.8.1"
+24 -13
View File
@@ -124,13 +124,24 @@ pub fn image_size(f: &NamedTempFile) -> Option<(usize, usize)> {
} }
/// Determine size of image with buffer /// Determine size of image with buffer
pub fn image_size_vec(v: &[u8]) -> Option<(usize, usize)> { pub fn image_size_vec(v: &[u8], mime: &str) -> Option<(usize, usize)> {
if let Ok(size) = imagesize::blob_size(v) match mime {
.inspect_err(|err| tracing::error!("Failed to generate image size! {err:?}")) "image/svg+xml" => {
{ let tree =
Some((size.width, size.height)) report_internal_error!(usvg::Tree::from_data(v, &Default::default())).ok()?;
} else {
None let size = tree.size();
Some((size.width() as usize, size.height() as usize))
}
_ => {
if let Ok(size) = imagesize::blob_size(v)
.inspect_err(|err| tracing::error!("Failed to generate image size! {err:?}"))
{
Some((size.width, size.height))
} else {
None
}
}
} }
} }
@@ -167,7 +178,7 @@ pub fn decode_image<R: Read + BufRead + Seek>(reader: &mut R, mime: &str) -> Res
jxl_image.height(), jxl_image.height(),
frame.image().buf().to_vec(), frame.image().buf().to_vec(),
) )
.ok_or_else(|| create_error!(LabelMe))?, .ok_or_else(|| create_error!(ImageProcessingFailed))?,
) )
.to_rgb8(), .to_rgb8(),
)), )),
@@ -178,14 +189,14 @@ pub fn decode_image<R: Read + BufRead + Seek>(reader: &mut R, mime: &str) -> Res
jxl_image.height(), jxl_image.height(),
frame.image().buf().to_vec(), frame.image().buf().to_vec(),
) )
.ok_or_else(|| create_error!(LabelMe))?, .ok_or_else(|| create_error!(ImageProcessingFailed))?,
) )
.to_rgba8(), .to_rgba8(),
)), )),
_ => Err(create_error!(LabelMe)), _ => Err(create_error!(ImageProcessingFailed)),
} }
} else { } else {
Err(create_error!(LabelMe)) Err(create_error!(ImageProcessingFailed))
} }
} }
// Read image using resvg // Read image using resvg
@@ -197,7 +208,7 @@ pub fn decode_image<R: Read + BufRead + Seek>(reader: &mut R, mime: &str) -> Res
let tree = report_internal_error!(usvg::Tree::from_data(&buf, &Default::default()))?; let tree = report_internal_error!(usvg::Tree::from_data(&buf, &Default::default()))?;
let size = tree.size(); let size = tree.size();
let mut pixmap = Pixmap::new(size.width() as u32, size.height() as u32) let mut pixmap = Pixmap::new(size.width() as u32, size.height() as u32)
.ok_or_else(|| create_error!(LabelMe))?; .ok_or_else(|| create_error!(ImageProcessingFailed))?;
let mut pixmap_mut = pixmap.as_mut(); let mut pixmap_mut = pixmap.as_mut();
resvg::render(&tree, Default::default(), &mut pixmap_mut); resvg::render(&tree, Default::default(), &mut pixmap_mut);
@@ -208,7 +219,7 @@ pub fn decode_image<R: Read + BufRead + Seek>(reader: &mut R, mime: &str) -> Res
size.height() as u32, size.height() as u32,
pixmap.data().to_vec(), pixmap.data().to_vec(),
) )
.ok_or_else(|| create_error!(LabelMe))?, .ok_or_else(|| create_error!(ImageProcessingFailed))?,
)) ))
} }
// Check if we can read using image-rs crate // Check if we can read using image-rs crate
+3 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-models" name = "revolt-models"
version = "0.7.18" version = "0.7.19"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
@@ -20,8 +20,8 @@ default = ["serde", "partials", "rocket"]
[dependencies] [dependencies]
# Core # Core
revolt-config = { version = "0.7.18", path = "../config" } revolt-config = { version = "0.7.19", path = "../config" }
revolt-permissions = { version = "0.7.18", path = "../permissions" } revolt-permissions = { version = "0.7.19", path = "../permissions" }
# Utility # Utility
regex = "1" regex = "1"
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-permissions" name = "revolt-permissions"
version = "0.7.18" version = "0.7.19"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
@@ -21,7 +21,7 @@ async-std = { version = "1.8.0", features = ["attributes"] }
[dependencies] [dependencies]
# Core # Core
revolt-result = { version = "0.7.18", path = "../result" } revolt-result = { version = "0.7.19", path = "../result" }
# Utility # Utility
auto_ops = "0.3.0" auto_ops = "0.3.0"
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-presence" name = "revolt-presence"
version = "0.7.18" version = "0.7.19"
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>"]
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-result" name = "revolt-result"
version = "0.7.18" version = "0.7.19"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
+3
View File
@@ -1,4 +1,5 @@
use axum::{http::StatusCode, response::IntoResponse, Json}; use axum::{http::StatusCode, response::IntoResponse, Json};
use rocket::http::Status;
use crate::{Error, ErrorType}; use crate::{Error, ErrorType};
@@ -78,6 +79,8 @@ impl IntoResponse for Error {
ErrorType::FileTooSmall => StatusCode::UNPROCESSABLE_ENTITY, ErrorType::FileTooSmall => StatusCode::UNPROCESSABLE_ENTITY,
ErrorType::FileTooLarge { .. } => StatusCode::UNPROCESSABLE_ENTITY, ErrorType::FileTooLarge { .. } => StatusCode::UNPROCESSABLE_ENTITY,
ErrorType::FileTypeNotAllowed => StatusCode::BAD_REQUEST, ErrorType::FileTypeNotAllowed => StatusCode::BAD_REQUEST,
ErrorType::ImageProcessingFailed => StatusCode::INTERNAL_SERVER_ERROR,
ErrorType::NoEmbedData => StatusCode::BAD_REQUEST,
}; };
(status, Json(&self)).into_response() (status, Json(&self)).into_response()
+2
View File
@@ -162,6 +162,8 @@ pub enum ErrorType {
max: usize, max: usize,
}, },
FileTypeNotAllowed, FileTypeNotAllowed,
ImageProcessingFailed,
NoEmbedData,
// ? Legacy errors // ? Legacy errors
VosoUnavailable, VosoUnavailable,
+2
View File
@@ -84,6 +84,8 @@ impl<'r> Responder<'r, 'static> for Error {
ErrorType::FileTooSmall => Status::UnprocessableEntity, ErrorType::FileTooSmall => Status::UnprocessableEntity,
ErrorType::FileTooLarge { .. } => Status::UnprocessableEntity, ErrorType::FileTooLarge { .. } => Status::UnprocessableEntity,
ErrorType::FileTypeNotAllowed => Status::BadRequest, ErrorType::FileTypeNotAllowed => Status::BadRequest,
ErrorType::ImageProcessingFailed => Status::InternalServerError,
ErrorType::NoEmbedData => Status::BadRequest,
}; };
// Serialize the error data structure into JSON. // Serialize the error data structure into JSON.
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-delta" name = "revolt-delta"
version = "0.7.18" version = "0.7.19"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
authors = ["Paul Makles <paulmakles@gmail.com>"] authors = ["Paul Makles <paulmakles@gmail.com>"]
edition = "2018" edition = "2018"
+5 -5
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-autumn" name = "revolt-autumn"
version = "0.7.18" version = "0.7.19"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
@@ -42,12 +42,12 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# Core crates # Core crates
revolt-files = { version = "0.7.18", path = "../../core/files" } revolt-files = { version = "0.7.19", path = "../../core/files" }
revolt-config = { version = "0.7.18", path = "../../core/config" } revolt-config = { version = "0.7.19", path = "../../core/config" }
revolt-database = { version = "0.7.18", path = "../../core/database", features = [ revolt-database = { version = "0.7.19", path = "../../core/database", features = [
"axum-impl", "axum-impl",
] } ] }
revolt-result = { version = "0.7.18", path = "../../core/result", features = [ revolt-result = { version = "0.7.19", path = "../../core/result", features = [
"utoipa", "utoipa",
"axum", "axum",
] } ] }
+5 -5
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-january" name = "revolt-january"
version = "0.7.18" version = "0.7.19"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
@@ -31,13 +31,13 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# Core crates # Core crates
revolt-config = { version = "0.7.18", path = "../../core/config" } revolt-config = { version = "0.7.19", path = "../../core/config" }
revolt-models = { version = "0.7.18", path = "../../core/models" } revolt-models = { version = "0.7.19", path = "../../core/models" }
revolt-result = { version = "0.7.18", path = "../../core/result", features = [ revolt-result = { version = "0.7.19", path = "../../core/result", features = [
"utoipa", "utoipa",
"axum", "axum",
] } ] }
revolt-files = { version = "0.7.18", path = "../../core/files" } revolt-files = { version = "0.7.19", path = "../../core/files" }
# Axum / web server # Axum / web server
axum = { version = "0.7.5" } axum = { version = "0.7.5" }
+7 -2
View File
@@ -1,6 +1,7 @@
use axum::{extract::Query, response::IntoResponse, routing::get, Json, Router}; use axum::{extract::Query, response::IntoResponse, routing::get, Json, Router};
use reqwest::header; use reqwest::header;
use revolt_result::Result; use revolt_models::v0::Embed;
use revolt_result::{create_error, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use utoipa::ToSchema; use utoipa::ToSchema;
@@ -87,5 +88,9 @@ async fn embed(
Query(UrlQuery { url }): Query<UrlQuery>, Query(UrlQuery { url }): Query<UrlQuery>,
// TypedHeader(Authorization(_bearer)): TypedHeader<Authorization<Bearer>>, // TypedHeader(Authorization(_bearer)): TypedHeader<Authorization<Bearer>>,
) -> Result<impl IntoResponse> { ) -> Result<impl IntoResponse> {
Request::generate_embed(url).await.map(Json) match Request::generate_embed(url).await {
Ok(Embed::None) => Err(create_error!(NoEmbedData)),
result => result,
}
.map(Json)
} }
+15 -14
View File
@@ -66,7 +66,7 @@ impl Request {
let Request { response, mime } = Request::new(url).await?; let Request { response, mime } = Request::new(url).await?;
if matches!(mime.type_(), mime::IMAGE | mime::VIDEO) { if matches!(mime.type_(), mime::IMAGE | mime::VIDEO) {
let bytes = response.bytes().await.map_err(|_| create_error!(LabelMe)); let bytes = report_internal_error!(response.bytes().await);
let result = match bytes { let result = match bytes {
Ok(bytes) => { Ok(bytes) => {
@@ -77,7 +77,7 @@ impl Request {
if is_valid_image(reader, "image/gif") { if is_valid_image(reader, "image/gif") {
Ok(("image/gif".to_owned(), bytes.to_vec())) Ok(("image/gif".to_owned(), bytes.to_vec()))
} else { } else {
Err(create_error!(LabelMe)) Err(create_error!(FileTypeNotAllowed))
} }
} else { } else {
Ok(( Ok((
@@ -96,7 +96,7 @@ impl Request {
if video_size(&file).is_some() { if video_size(&file).is_some() {
Ok((mime.to_string(), bytes.to_vec())) Ok((mime.to_string(), bytes.to_vec()))
} else { } else {
Err(create_error!(LabelMe)) Err(create_error!(FileTypeNotAllowed))
} }
} }
} }
@@ -106,7 +106,7 @@ impl Request {
PROXY_CACHE.insert(url.to_owned(), result.clone()).await; PROXY_CACHE.insert(url.to_owned(), result.clone()).await;
result result
} else { } else {
Err(create_error!(LabelMe)) Err(create_error!(FileTypeNotAllowed))
} }
} }
} }
@@ -122,20 +122,21 @@ impl Request {
_ => Ok(None), _ => Ok(None),
} }
} else { } else {
let response = if let Some(Request { response, .. }) = request { let request = if let Some(request) = request {
response request
} else { } else {
let Request { response, mime } = Request::new(url).await?; let request = Request::new(url).await?;
if matches!(mime.type_(), mime::IMAGE) { if matches!(request.mime.type_(), mime::IMAGE) {
response request
} else { } else {
return Err(create_error!(LabelMe)); return Err(create_error!(FileTypeNotAllowed));
} }
}; };
if let Some((width, height)) = if let Some((width, height)) = image_size_vec(
image_size_vec(&report_internal_error!(response.bytes().await)?) &report_internal_error!(request.response.bytes().await)?,
{ request.mime.as_ref(),
) {
Ok(Some(Image { Ok(Some(Image {
url: url.to_owned(), url: url.to_owned(),
width, width,
@@ -166,7 +167,7 @@ impl Request {
if matches!(mime.type_(), mime::VIDEO) { if matches!(mime.type_(), mime::VIDEO) {
response response
} else { } else {
return Err(create_error!(LabelMe)); return Err(create_error!(FileTypeNotAllowed));
} }
}; };
+65 -53
View File
@@ -9,34 +9,38 @@ use scraper::{Html, Selector};
/// Create website metadata from URL and document /// Create website metadata from URL and document
pub async fn create_website_embed(original_url: &str, document: &str) -> Option<WebsiteMetadata> { pub async fn create_website_embed(original_url: &str, document: &str) -> Option<WebsiteMetadata> {
let document = Html::parse_document(document); let (mut meta, mut link) = {
let document = Html::parse_document(document);
// create selectors // create selectors
let meta_selector = Selector::parse("meta").ok()?; let meta_selector = Selector::parse("meta").ok()?;
let link_selector = Selector::parse("link").ok()?; let link_selector = Selector::parse("link").ok()?;
// extract meta tags // extract meta tags
let mut meta = HashMap::new(); let mut meta = HashMap::new();
for el in document.select(&meta_selector) { for el in document.select(&meta_selector) {
let node = el.value(); let node = el.value();
if let (Some(property), Some(content)) = ( if let (Some(property), Some(content)) = (
node.attr("property").or_else(|| node.attr("name")), node.attr("property").or_else(|| node.attr("name")),
node.attr("content"), node.attr("content"),
) { ) {
meta.insert(property.to_string(), content.to_string()); meta.insert(property.to_string(), content.to_string());
}
} }
}
// extract rel links // extract rel links
let mut link = HashMap::new(); let mut link = HashMap::new();
for el in document.select(&link_selector) { for el in document.select(&link_selector) {
let node = el.value(); let node = el.value();
if let (Some(property), Some(content)) = (node.attr("rel"), node.attr("href")) { if let (Some(property), Some(content)) = (node.attr("rel"), node.attr("href")) {
link.insert(property.to_string(), content.to_string()); link.insert(property.to_string(), content.to_string());
}
} }
}
(meta, link)
};
// build metadata // build metadata
let mut metadata = WebsiteMetadata { let mut metadata = WebsiteMetadata {
@@ -137,32 +141,41 @@ pub async fn create_website_embed(original_url: &str, document: &str) -> Option<
}; };
// populate extra metadata for popular websites // populate extra metadata for popular websites
populate_special(original_url.to_owned(), &mut metadata); populate_special(original_url.to_owned(), &mut metadata).await;
// TODO: these do not work because compiler is tripping:
// fetch video size if missing // fetch video size if missing
// if let Some(Video { width, height, url }) = &metadata.video { if metadata.special.is_none() {
// if width == &0 || height == &0 { if let Some(Video { width, height, url }) = &metadata.video {
// metadata.video = match crate::requests::Request::fetch_video_metadata(url, None).await { if width == &0 || height == &0 {
// Ok(Some(video)) => Some(video), metadata.video =
// _ => None, match crate::requests::Request::fetch_video_metadata(url, None).await {
// } Ok(Some(video)) => Some(video),
// } _ => None,
// } }
}
}
}
// remove image if video exists
if metadata.video.is_some() {
metadata.image.take();
}
// fetch image size if missing // fetch image size if missing
// if let Some(Image { if metadata.special.is_none() {
// width, height, url, .. if let Some(Image {
// }) = &metadata.image width, height, url, ..
// { }) = &metadata.image
// if width == &0 || height == &0 { {
// metadata.image = match crate::requests::Request::fetch_image_metadata(url, None).await { if width == &0 || height == &0 {
// Ok(Some(image)) => Some(image), metadata.image =
// _ => None, match crate::requests::Request::fetch_image_metadata(url, None).await {
// } Ok(Some(image)) => Some(image),
// } _ => None,
// } }
}
}
}
// truncate data // truncate data
metadata.truncate(); metadata.truncate();
@@ -175,7 +188,7 @@ pub async fn create_website_embed(original_url: &str, document: &str) -> Option<
} }
} }
pub fn populate_special(original_url: String, metadata: &mut WebsiteMetadata) { pub async fn populate_special(original_url: String, metadata: &mut WebsiteMetadata) {
lazy_static! { lazy_static! {
static ref RE_YOUTUBE: Regex = Regex::new("^(?:(?:https?:)?//)?(?:(?:www|m)\\.)?(?:(?:youtube\\.com|youtu.be))(?:/(?:[\\w\\-]+\\?v=|embed/|v/)?)([\\w\\-]+)(?:\\S+)?$").unwrap(); static ref RE_YOUTUBE: Regex = Regex::new("^(?:(?:https?:)?//)?(?:(?:www|m)\\.)?(?:(?:youtube\\.com|youtu.be))(?:/(?:[\\w\\-]+\\?v=|embed/|v/)?)([\\w\\-]+)(?:\\S+)?$").unwrap();
@@ -223,15 +236,14 @@ pub fn populate_special(original_url: String, metadata: &mut WebsiteMetadata) {
metadata.site_name.take(); metadata.site_name.take();
// Verify the video exists // Verify the video exists
// TODO: breaks axum :( if !crate::requests::Request::exists(&format!(
// if !crate::requests::Request::exists(&format!( "http://img.youtube.com/vi/{}/sddefault.jpg",
// "http://img.youtube.com/vi/{}/sddefault.jpg", id
// id ))
// )) .await
// .await {
// { return;
// return; }
// }
} }
if let Some(timestamp_captures) = RE_TIMESTAMP.captures_iter(url).next() { if let Some(timestamp_captures) = RE_TIMESTAMP.captures_iter(url).next() {
+1
View File
@@ -2,6 +2,7 @@
- [Introduction](./hello.md) - [Introduction](./hello.md)
- [Project Structure]() - [Project Structure]()
- [Creating new API features](./new_features.md)
- [Testing]() - [Testing]()
- [Writing a new database test]() - [Writing a new database test]()
- [Writing a new API test]() - [Writing a new API test]()
+15
View File
@@ -0,0 +1,15 @@
# New API features
New API features must be documented where appropriate, this document aims to cover everywhere you need to update for new features.
Before writing new API features, generally a good idea to:
- Consult with other developers in the [Revolt Developers space](https://rvlt.gg/API)
- If it's a relatively big feature, also [write an RFC](https://github.com/revoltchat/rfcs)
When your feature is ready to release, ensure to:
- Update backend documentation (what you're reading now!) if applicable
- Update the [developers documentation](https://github.com/revoltchat/wiki) if applicable
- Update the Feature Matrix (or ask someone that can to do so)
- Ensure it is properly listed in the backend release changelog
+2
View File
@@ -24,6 +24,7 @@ docker build -t ghcr.io/revoltchat/base:latest -f Dockerfile.useCurrentArch .
docker build -t ghcr.io/revoltchat/server:$TAG - < crates/delta/Dockerfile docker build -t ghcr.io/revoltchat/server:$TAG - < crates/delta/Dockerfile
docker build -t ghcr.io/revoltchat/bonfire:$TAG - < crates/bonfire/Dockerfile docker build -t ghcr.io/revoltchat/bonfire:$TAG - < crates/bonfire/Dockerfile
docker build -t ghcr.io/revoltchat/autumn:$TAG - < crates/services/autumn/Dockerfile docker build -t ghcr.io/revoltchat/autumn:$TAG - < crates/services/autumn/Dockerfile
docker build -t ghcr.io/revoltchat/january:$TAG - < crates/services/january/Dockerfile
if [ "$DEBUG" = "true" ]; then if [ "$DEBUG" = "true" ]; then
git restore Cargo.toml git restore Cargo.toml
@@ -32,3 +33,4 @@ fi
docker push ghcr.io/revoltchat/server:$TAG docker push ghcr.io/revoltchat/server:$TAG
docker push ghcr.io/revoltchat/bonfire:$TAG docker push ghcr.io/revoltchat/bonfire:$TAG
docker push ghcr.io/revoltchat/autumn:$TAG docker push ghcr.io/revoltchat/autumn:$TAG
docker push ghcr.io/revoltchat/january:$TAG