parent
79338256df
commit
1a1d85b898
1067
Cargo.lock
generated
1067
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
25
Cargo.toml
25
Cargo.toml
@ -1,35 +1,38 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "void_cat"
|
name = "void_cat"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# 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]
|
||||||
labels = ["dep:candle-core", "dep:candle-nn", "dep:candle-transformers"]
|
default = ["nip96", "blossom"]
|
||||||
|
labels = ["nip96", "dep:candle-core", "dep:candle-nn", "dep:candle-transformers"]
|
||||||
|
nip96 = ["dep:ffmpeg-sys-the-third", "dep:blurhash", "dep:libc"]
|
||||||
|
blossom = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log = "0.4.21"
|
log = "0.4.21"
|
||||||
nostr = "0.30.0"
|
nostr = "0.34.1"
|
||||||
pretty_env_logger = "0.5.0"
|
pretty_env_logger = "0.5.0"
|
||||||
rocket = { version = "0.5.0", features = ["json"] }
|
rocket = { version = "0.5.0", features = ["json"] }
|
||||||
tokio = { version = "1.37.0", features = ["rt", "rt-multi-thread", "macros"] }
|
tokio = { version = "1.37.0", features = ["rt", "rt-multi-thread", "macros"] }
|
||||||
base64 = "0.21.7"
|
base64 = "0.22.1"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
serde = { version = "1.0.198", features = ["derive"] }
|
serde = { version = "1.0.198", features = ["derive"] }
|
||||||
uuid = { version = "1.8.0", features = ["v4"] }
|
uuid = { version = "1.8.0", features = ["v4"] }
|
||||||
anyhow = "1.0.82"
|
anyhow = "1.0.82"
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
sqlx = { version = "0.7.4", features = ["mysql", "runtime-tokio", "chrono"] }
|
sqlx = { version = "0.8.1", features = ["mysql", "runtime-tokio", "chrono"] }
|
||||||
config = { version = "0.14.0", features = ["toml"] }
|
config = { version = "0.14.0", features = ["toml"] }
|
||||||
chrono = { version = "0.4.38", features = ["serde"] }
|
chrono = { version = "0.4.38", features = ["serde"] }
|
||||||
ffmpeg-sys-the-third = { version = "1.1.1",features = ["default"] }
|
ffmpeg-sys-the-third = { version = "1.1.1", features = ["default"], optional = true }
|
||||||
libc = "0.2.153"
|
libc = { version = "0.2.153", optional = true }
|
||||||
blurhash = "0.2.1"
|
blurhash = { version = "0.2.1", optional = true }
|
||||||
ureq = { version = "2.9.7", features = ["json"] }
|
ureq = { version = "2.9.7", features = ["json"] }
|
||||||
url = "2.5.0"
|
url = "2.5.0"
|
||||||
serde_with = { version = "3.8.1", features = ["hex"] }
|
serde_with = { version = "3.8.1", features = ["hex"] }
|
||||||
candle-core = { git = "https://github.com/huggingface/candle.git", version = "0.5.1", optional = true }
|
candle-core = { git = "https://github.com/huggingface/candle.git", version = "^0.6.1", optional = true }
|
||||||
candle-nn = { git = "https://github.com/huggingface/candle.git", version = "0.5.1", optional = true }
|
candle-nn = { git = "https://github.com/huggingface/candle.git", version = "^0.6.1", optional = true }
|
||||||
candle-transformers = { git = "https://github.com/huggingface/candle.git", version = "0.5.1", optional = true }
|
candle-transformers = { git = "https://github.com/huggingface/candle.git", version = "^0.6.1", optional = true }
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use base64::prelude::*;
|
use base64::prelude::*;
|
||||||
use log::info;
|
use log::info;
|
||||||
use nostr::{Event, JsonUtil, Kind, Tag, Timestamp};
|
use nostr::{Event, JsonUtil, Kind, Tag, TagKind, Timestamp};
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
use rocket::request::{FromRequest, Outcome};
|
use rocket::request::{FromRequest, Outcome};
|
||||||
use rocket::{async_trait, Request};
|
use rocket::{async_trait, Request};
|
||||||
@ -15,7 +15,7 @@ impl<'r> FromRequest<'r> for BlossomAuth {
|
|||||||
type Error = &'static str;
|
type Error = &'static str;
|
||||||
|
|
||||||
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||||
return if let Some(auth) = request.headers().get_one("authorization") {
|
if let Some(auth) = request.headers().get_one("authorization") {
|
||||||
if auth.starts_with("Nostr ") {
|
if auth.starts_with("Nostr ") {
|
||||||
let event = if let Ok(j) = BASE64_STANDARD.decode(auth[6..].to_string()) {
|
let event = if let Ok(j) = BASE64_STANDARD.decode(auth[6..].to_string()) {
|
||||||
if let Ok(ev) = Event::from_json(j) {
|
if let Ok(ev) = Event::from_json(j) {
|
||||||
@ -38,11 +38,13 @@ impl<'r> FromRequest<'r> for BlossomAuth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check expiration tag
|
// check expiration tag
|
||||||
if let Some(expiration) = event.tags.iter().find_map(|t| match t {
|
if let Some(expiration) = event.tags.iter().find_map(|t| if t.kind() == TagKind::Expiration {
|
||||||
Tag::Expiration(v) => Some(v),
|
t.content()
|
||||||
_ => None,
|
} else {
|
||||||
|
None
|
||||||
}) {
|
}) {
|
||||||
if *expiration <= Timestamp::now() {
|
let u_exp: Timestamp = expiration.parse().unwrap();
|
||||||
|
if u_exp <= Timestamp::now() {
|
||||||
return Outcome::Error((Status::new(401), "Expiration invalid"));
|
return Outcome::Error((Status::new(401), "Expiration invalid"));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -69,6 +71,6 @@ impl<'r> FromRequest<'r> for BlossomAuth {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Outcome::Error((Status::new(403), "Auth header not found"))
|
Outcome::Error((Status::new(403), "Auth header not found"))
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeekExt};
|
|||||||
#[cfg(feature = "labels")]
|
#[cfg(feature = "labels")]
|
||||||
use crate::db::FileLabel;
|
use crate::db::FileLabel;
|
||||||
use crate::db::FileUpload;
|
use crate::db::FileUpload;
|
||||||
|
#[cfg(feature = "nip96")]
|
||||||
use crate::processing::{compress_file, FileProcessorResult, probe_file, ProbeStream};
|
use crate::processing::{compress_file, FileProcessorResult, probe_file, ProbeStream};
|
||||||
#[cfg(feature = "labels")]
|
#[cfg(feature = "labels")]
|
||||||
use crate::processing::labeling::label_frame;
|
use crate::processing::labeling::label_frame;
|
||||||
@ -86,6 +87,7 @@ impl FileStore {
|
|||||||
|
|
||||||
info!("File saved to temp path: {}", tmp_path.to_str().unwrap());
|
info!("File saved to temp path: {}", tmp_path.to_str().unwrap());
|
||||||
|
|
||||||
|
#[cfg(feature = "nip96")]
|
||||||
if compress {
|
if compress {
|
||||||
let start = SystemTime::now();
|
let start = SystemTime::now();
|
||||||
let proc_result = compress_file(tmp_path.clone(), mime_type)?;
|
let proc_result = compress_file(tmp_path.clone(), mime_type)?;
|
||||||
|
28
src/main.rs
28
src/main.rs
@ -20,9 +20,10 @@ mod blob;
|
|||||||
mod cors;
|
mod cors;
|
||||||
mod db;
|
mod db;
|
||||||
mod filesystem;
|
mod filesystem;
|
||||||
|
#[cfg(feature = "nip96")]
|
||||||
|
mod processing;
|
||||||
mod routes;
|
mod routes;
|
||||||
mod settings;
|
mod settings;
|
||||||
mod processing;
|
|
||||||
mod webhook;
|
mod webhook;
|
||||||
|
|
||||||
#[rocket::main]
|
#[rocket::main]
|
||||||
@ -44,7 +45,7 @@ async fn main() -> Result<(), Error> {
|
|||||||
let mut config = rocket::Config::default();
|
let mut config = rocket::Config::default();
|
||||||
let ip: SocketAddr = match &settings.listen {
|
let ip: SocketAddr = match &settings.listen {
|
||||||
Some(i) => i.parse().unwrap(),
|
Some(i) => i.parse().unwrap(),
|
||||||
None => SocketAddr::new(IpAddr::from([0, 0, 0, 0]), 8000)
|
None => SocketAddr::new(IpAddr::from([0, 0, 0, 0]), 8000),
|
||||||
};
|
};
|
||||||
config.address = ip.ip();
|
config.address = ip.ip();
|
||||||
config.port = ip.port();
|
config.port = ip.port();
|
||||||
@ -56,20 +57,27 @@ async fn main() -> Result<(), Error> {
|
|||||||
.limit("form", upload_limit);
|
.limit("form", upload_limit);
|
||||||
config.ident = Ident::try_new("void-cat-rs").unwrap();
|
config.ident = Ident::try_new("void-cat-rs").unwrap();
|
||||||
|
|
||||||
let rocket = rocket::Rocket::custom(config)
|
let mut rocket = rocket::Rocket::custom(config)
|
||||||
.manage(FileStore::new(settings.clone()))
|
.manage(FileStore::new(settings.clone()))
|
||||||
.manage(settings.clone())
|
.manage(settings.clone())
|
||||||
.manage(db.clone())
|
.manage(db.clone())
|
||||||
.manage(settings.webhook_url.as_ref().map(|w| Webhook::new(w.clone())))
|
.manage(
|
||||||
|
settings
|
||||||
|
.webhook_url
|
||||||
|
.as_ref()
|
||||||
|
.map(|w| Webhook::new(w.clone())),
|
||||||
|
)
|
||||||
.attach(CORS)
|
.attach(CORS)
|
||||||
.attach(Shield::new()) // disable
|
.attach(Shield::new()) // disable
|
||||||
.mount("/", routes::blossom_routes())
|
.mount("/", routes![root, get_blob, head_blob]);
|
||||||
.mount("/", routes::nip96_routes())
|
|
||||||
.mount("/", routes![root, get_blob, head_blob])
|
|
||||||
.launch()
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if let Err(e) = rocket {
|
#[cfg(feature = "blossom")] {
|
||||||
|
rocket = rocket.mount("/", routes::blossom_routes());
|
||||||
|
}
|
||||||
|
#[cfg(feature = "nip96")] {
|
||||||
|
rocket = rocket.mount("/", routes::nip96_routes());
|
||||||
|
}
|
||||||
|
if let Err(e) = rocket.launch().await {
|
||||||
error!("Rocker error {}", e);
|
error!("Rocker error {}", e);
|
||||||
Err(Error::from(e))
|
Err(Error::from(e))
|
||||||
} else {
|
} else {
|
||||||
|
@ -4,8 +4,8 @@ use std::ptr;
|
|||||||
|
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use ffmpeg_sys_the_third::{av_frame_alloc, AVFrame, AVPixelFormat, sws_freeContext, sws_getContext, sws_scale_frame};
|
use ffmpeg_sys_the_third::{av_frame_alloc, AVFrame, AVPixelFormat, sws_freeContext, sws_getContext, sws_scale_frame};
|
||||||
use crate::processing::probe::FFProbe;
|
|
||||||
|
|
||||||
|
use crate::processing::probe::FFProbe;
|
||||||
use crate::processing::webp::WebpProcessor;
|
use crate::processing::webp::WebpProcessor;
|
||||||
|
|
||||||
mod webp;
|
mod webp;
|
||||||
|
@ -2,17 +2,17 @@ use std::fs;
|
|||||||
|
|
||||||
use log::error;
|
use log::error;
|
||||||
use nostr::prelude::hex;
|
use nostr::prelude::hex;
|
||||||
use nostr::Tag;
|
use nostr::{Tag, TagKind};
|
||||||
use rocket::{Data, Route, routes, State};
|
|
||||||
use rocket::data::ByteUnit;
|
use rocket::data::ByteUnit;
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
use rocket::response::Responder;
|
use rocket::response::Responder;
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
|
use rocket::{routes, Data, Route, State};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::auth::blossom::BlossomAuth;
|
use crate::auth::blossom::BlossomAuth;
|
||||||
use crate::blob::BlobDescriptor;
|
use crate::blob::BlobDescriptor;
|
||||||
use crate::db::{Database};
|
use crate::db::Database;
|
||||||
use crate::filesystem::FileStore;
|
use crate::filesystem::FileStore;
|
||||||
use crate::routes::delete_file;
|
use crate::routes::delete_file;
|
||||||
use crate::settings::Settings;
|
use crate::settings::Settings;
|
||||||
@ -54,9 +54,10 @@ impl BlossomResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn check_method(event: &nostr::Event, method: &str) -> bool {
|
fn check_method(event: &nostr::Event, method: &str) -> bool {
|
||||||
if let Some(t) = event.tags.iter().find_map(|t| match t {
|
if let Some(t) = event.tags.iter().find_map(|t| if t.kind() == TagKind::Method {
|
||||||
Tag::Hashtag(tag) => Some(tag),
|
t.content()
|
||||||
_ => None,
|
} else {
|
||||||
|
None
|
||||||
}) {
|
}) {
|
||||||
return t == method;
|
return t == method;
|
||||||
}
|
}
|
||||||
@ -89,10 +90,11 @@ async fn upload(
|
|||||||
return BlossomResponse::error("Invalid request method tag");
|
return BlossomResponse::error("Invalid request method tag");
|
||||||
}
|
}
|
||||||
|
|
||||||
let name = auth.event.tags.iter().find_map(|t| match t {
|
let name = auth
|
||||||
Tag::Name(s) => Some(s.clone()),
|
.event
|
||||||
_ => None,
|
.tags
|
||||||
});
|
.iter()
|
||||||
|
.find_map(|t| if t.kind() == TagKind::Name { t.content() } else { None });
|
||||||
let size = match auth.event.tags.iter().find_map(|t| {
|
let size = match auth.event.tags.iter().find_map(|t| {
|
||||||
let values = t.as_vec();
|
let values = t.as_vec();
|
||||||
if values.len() == 2 && values[0] == "size" {
|
if values.len() == 2 && values[0] == "size" {
|
||||||
@ -102,7 +104,7 @@ async fn upload(
|
|||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => return BlossomResponse::error("Invalid request, no size tag")
|
None => return BlossomResponse::error("Invalid request, no size tag"),
|
||||||
};
|
};
|
||||||
if size > settings.max_upload_bytes {
|
if size > settings.max_upload_bytes {
|
||||||
return BlossomResponse::error("File too large");
|
return BlossomResponse::error("File too large");
|
||||||
@ -118,22 +120,31 @@ async fn upload(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
match fs
|
match fs
|
||||||
.put(data.open(ByteUnit::from(settings.max_upload_bytes)), &mime_type, false)
|
.put(
|
||||||
|
data.open(ByteUnit::from(settings.max_upload_bytes)),
|
||||||
|
&mime_type,
|
||||||
|
false,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(mut blob) => {
|
Ok(mut blob) => {
|
||||||
blob.upload.name = name.unwrap_or("".to_string());
|
blob.upload.name = name.unwrap_or("").to_owned();
|
||||||
|
|
||||||
let pubkey_vec = auth.event.pubkey.to_bytes().to_vec();
|
let pubkey_vec = auth.event.pubkey.to_bytes().to_vec();
|
||||||
if let Some(wh) = webhook.as_ref() {
|
if let Some(wh) = webhook.as_ref() {
|
||||||
match wh.store_file(&pubkey_vec, blob.clone()) {
|
match wh.store_file(&pubkey_vec, blob.clone()) {
|
||||||
Ok(store) => if !store {
|
Ok(store) => {
|
||||||
let _ = fs::remove_file(blob.path);
|
if !store {
|
||||||
return BlossomResponse::error("Upload rejected");
|
let _ = fs::remove_file(blob.path);
|
||||||
|
return BlossomResponse::error("Upload rejected");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let _ = fs::remove_file(blob.path);
|
let _ = fs::remove_file(blob.path);
|
||||||
return BlossomResponse::error(format!("Internal error, failed to call webhook: {}", e));
|
return BlossomResponse::error(format!(
|
||||||
|
"Internal error, failed to call webhook: {}",
|
||||||
|
e
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,14 @@ use rocket::response::Responder;
|
|||||||
|
|
||||||
use crate::db::{Database, FileUpload};
|
use crate::db::{Database, FileUpload};
|
||||||
use crate::filesystem::FileStore;
|
use crate::filesystem::FileStore;
|
||||||
|
#[cfg(feature = "blossom")]
|
||||||
pub use crate::routes::blossom::blossom_routes;
|
pub use crate::routes::blossom::blossom_routes;
|
||||||
|
#[cfg(feature = "nip96")]
|
||||||
pub use crate::routes::nip96::nip96_routes;
|
pub use crate::routes::nip96::nip96_routes;
|
||||||
|
|
||||||
|
#[cfg(feature = "blossom")]
|
||||||
mod blossom;
|
mod blossom;
|
||||||
|
#[cfg(feature = "nip96")]
|
||||||
mod nip96;
|
mod nip96;
|
||||||
|
|
||||||
pub struct FilePayload {
|
pub struct FilePayload {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user