add support for blossom
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m42s
Rust / build (ubuntu-latest, stable) (pull_request) Failing after 1m53s
Rust / build (macos-latest, stable) (push) Has been cancelled
Rust / build (windows-latest, stable) (push) Has been cancelled
Rust / build (macos-latest, stable) (pull_request) Has been cancelled
Rust / build (windows-latest, stable) (pull_request) Has been cancelled
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m42s
Rust / build (ubuntu-latest, stable) (pull_request) Failing after 1m53s
Rust / build (macos-latest, stable) (push) Has been cancelled
Rust / build (windows-latest, stable) (push) Has been cancelled
Rust / build (macos-latest, stable) (pull_request) Has been cancelled
Rust / build (windows-latest, stable) (pull_request) Has been cancelled
This commit is contained in:
85
crates/state/src/blossom.rs
Normal file
85
crates/state/src/blossom.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{anyhow, Error};
|
||||
use gpui::AsyncApp;
|
||||
use gpui_tokio::Tokio;
|
||||
use mime_guess::from_path;
|
||||
use nostr_blossom::prelude::*;
|
||||
use nostr_sdk::prelude::*;
|
||||
|
||||
pub async fn upload(server: Url, path: PathBuf, cx: &AsyncApp) -> Result<Url, Error> {
|
||||
let content_type = from_path(&path).first_or_octet_stream().to_string();
|
||||
let data = smol::fs::read(path).await?;
|
||||
let keys = Keys::generate();
|
||||
|
||||
// Construct the blossom client
|
||||
let client = BlossomClient::new(server);
|
||||
|
||||
Tokio::spawn(cx, async move {
|
||||
let blob = client
|
||||
.upload_blob(data, Some(content_type), None, Some(&keys))
|
||||
.await?;
|
||||
|
||||
Ok(blob.url)
|
||||
})
|
||||
.await
|
||||
.map_err(|e| anyhow!("Upload error: {e}"))?
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_mime_type_detection() {
|
||||
// Test common file extensions
|
||||
assert_eq!(
|
||||
from_path("image.jpg").first_or_octet_stream().to_string(),
|
||||
"image/jpeg"
|
||||
);
|
||||
assert_eq!(
|
||||
from_path("document.pdf")
|
||||
.first_or_octet_stream()
|
||||
.to_string(),
|
||||
"application/pdf"
|
||||
);
|
||||
assert_eq!(
|
||||
from_path("page.html").first_or_octet_stream().to_string(),
|
||||
"text/html"
|
||||
);
|
||||
assert_eq!(
|
||||
from_path("data.json").first_or_octet_stream().to_string(),
|
||||
"application/json"
|
||||
);
|
||||
assert_eq!(
|
||||
from_path("script.js").first_or_octet_stream().to_string(),
|
||||
"text/javascript"
|
||||
);
|
||||
assert_eq!(
|
||||
from_path("style.css").first_or_octet_stream().to_string(),
|
||||
"text/css"
|
||||
);
|
||||
|
||||
// Test unknown extension falls back to octet-stream
|
||||
assert_eq!(
|
||||
from_path("unknown.xyz").first_or_octet_stream().to_string(),
|
||||
"chemical/x-xyz"
|
||||
);
|
||||
|
||||
// Test no extension falls back to octet-stream
|
||||
assert_eq!(
|
||||
from_path("file_without_extension")
|
||||
.first_or_octet_stream()
|
||||
.to_string(),
|
||||
"application/octet-stream"
|
||||
);
|
||||
|
||||
// Test truly unknown extension
|
||||
assert_eq!(
|
||||
from_path("unknown.unknown123")
|
||||
.first_or_octet_stream()
|
||||
.to_string(),
|
||||
"application/octet-stream"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -10,18 +10,18 @@ use nostr_connect::prelude::*;
|
||||
use nostr_lmdb::prelude::*;
|
||||
use nostr_sdk::prelude::*;
|
||||
|
||||
mod blossom;
|
||||
mod constants;
|
||||
mod device;
|
||||
mod gossip;
|
||||
mod nip05;
|
||||
mod nip96;
|
||||
mod signer;
|
||||
|
||||
pub use blossom::*;
|
||||
pub use constants::*;
|
||||
pub use device::*;
|
||||
pub use gossip::*;
|
||||
pub use nip05::*;
|
||||
pub use nip96::*;
|
||||
pub use signer::*;
|
||||
|
||||
pub fn init(window: &mut Window, cx: &mut App) {
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
use anyhow::anyhow;
|
||||
use nostr::hashes::sha256::Hash as Sha256Hash;
|
||||
use nostr::hashes::Hash;
|
||||
use nostr::prelude::*;
|
||||
use nostr_sdk::prelude::*;
|
||||
use reqwest::{multipart, Client as ReqClient, Response};
|
||||
|
||||
pub(crate) fn make_multipart_form(
|
||||
file_data: Vec<u8>,
|
||||
mime_type: Option<&str>,
|
||||
) -> Result<multipart::Form, anyhow::Error> {
|
||||
let form_file_part = multipart::Part::bytes(file_data).file_name("filename");
|
||||
|
||||
// Set the part's MIME type, or leave it as is if mime_type is None
|
||||
|
||||
let part = match mime_type {
|
||||
Some(mime) => form_file_part.mime_str(mime)?,
|
||||
None => form_file_part,
|
||||
};
|
||||
|
||||
Ok(multipart::Form::new().part("file", part))
|
||||
}
|
||||
|
||||
pub(crate) async fn upload<T>(
|
||||
signer: &T,
|
||||
desc: &ServerConfig,
|
||||
file_data: Vec<u8>,
|
||||
mime_type: Option<&str>,
|
||||
) -> Result<Url, anyhow::Error>
|
||||
where
|
||||
T: NostrSigner,
|
||||
{
|
||||
let payload: Sha256Hash = Sha256Hash::hash(&file_data);
|
||||
let data: HttpData = HttpData::new(desc.api_url.clone(), HttpMethod::POST).payload(payload);
|
||||
let nip98_auth: String = data.to_authorization(signer).await?;
|
||||
|
||||
// Make form
|
||||
let form: multipart::Form = make_multipart_form(file_data, mime_type)?;
|
||||
|
||||
// Make req client
|
||||
let req_client = ReqClient::new();
|
||||
|
||||
// Send
|
||||
let response: Response = req_client
|
||||
.post(desc.api_url.clone())
|
||||
.header("Authorization", nip98_auth)
|
||||
.multipart(form)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
// Parse response
|
||||
let json: Value = response.json().await?;
|
||||
let upload_response = nip96::UploadResponse::from_json(json.to_string())?;
|
||||
|
||||
if upload_response.status == UploadResponseStatus::Error {
|
||||
return Err(anyhow!(upload_response.message));
|
||||
}
|
||||
|
||||
Ok(upload_response.download_url()?.to_owned())
|
||||
}
|
||||
|
||||
pub async fn nostr_upload(
|
||||
client: &Client,
|
||||
server: &Url,
|
||||
file: Vec<u8>,
|
||||
) -> Result<Url, anyhow::Error> {
|
||||
let req_client = ReqClient::new();
|
||||
let config_url = nip96::get_server_config_url(server)?;
|
||||
|
||||
// Get
|
||||
let res = req_client.get(config_url.to_string()).send().await?;
|
||||
let json: Value = res.json().await?;
|
||||
|
||||
let config = nip96::ServerConfig::from_json(json.to_string())?;
|
||||
let signer = client
|
||||
.signer()
|
||||
.cloned()
|
||||
.unwrap_or(Keys::generate().into_nostr_signer());
|
||||
|
||||
let url = upload(&signer, &config, file, None).await?;
|
||||
|
||||
Ok(url)
|
||||
}
|
||||
Reference in New Issue
Block a user