Files
tiktok-dvm/src/blossom.rs
2025-02-27 12:50:04 +00:00

98 lines
2.7 KiB
Rust

use anyhow::Result;
use base64::Engine;
use log::info;
use nostr_sdk::{serde_json, EventBuilder, JsonUtil, Kind, NostrSigner, Tag, Timestamp};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::collections::HashMap;
use std::io::SeekFrom;
use std::ops::Add;
use std::path::PathBuf;
use tokio::fs::File;
use tokio::io::{AsyncReadExt, AsyncSeekExt};
use url::Url;
#[derive(Debug, Clone)]
pub struct Blossom {
url: Url,
client: reqwest::Client,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlobDescriptor {
pub url: String,
pub sha256: String,
pub size: u64,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
pub uploaded: u64,
#[serde(rename = "nip94", skip_serializing_if = "Option::is_none")]
pub nip94: Option<HashMap<String, String>>,
}
impl Blossom {
pub fn new(url: &str) -> Self {
Self {
url: url.parse().unwrap(),
client: reqwest::Client::new(),
}
}
async fn hash_file(f: &mut File) -> Result<String> {
let mut hash = Sha256::new();
let mut buf: [u8; 1024] = [0; 1024];
f.seek(SeekFrom::Start(0)).await?;
while let Ok(data) = f.read(&mut buf[..]).await {
if data == 0 {
break;
}
hash.update(&buf[..data]);
}
let hash = hash.finalize();
f.seek(SeekFrom::Start(0)).await?;
Ok(hex::encode(hash))
}
pub async fn upload<S>(
&self,
from_file: &PathBuf,
keys: &S,
mime: Option<&str>,
) -> Result<BlobDescriptor>
where
S: NostrSigner,
{
let mut f = File::open(from_file).await?;
let hash = Self::hash_file(&mut f).await?;
let auth_event = EventBuilder::new(Kind::Custom(24242), "Upload blob").tags([
Tag::hashtag("upload"),
Tag::parse(["x", &hash])?,
Tag::expiration(Timestamp::now().add(60)),
]);
let auth_event = auth_event.sign(keys).await?;
let rsp = self
.client
.put(self.url.join("/upload").unwrap())
.header("Content-Type", mime.unwrap_or("application/octet-stream"))
.header(
"Authorization",
&format!(
"Nostr {}",
base64::engine::general_purpose::STANDARD
.encode(auth_event.as_json().as_bytes())
),
)
.body(f)
.send()
.await?
.text()
.await?;
info!("Upload response: {}", rsp);
Ok(serde_json::from_str::<BlobDescriptor>(&rsp)?)
}
}