bevy_nostr 0.2.0

Bevy plugin for the Nostr protocol
Documentation
use ::url::Url;
use bevy::prelude::{Event, Message};
use ehttp::multipart::MultipartBuilder;
use nostr::NostrSigner;
use nostr::nips::nip96;
use nostr_sdk::prelude::*;
use std::io::Cursor;
use std::sync::Arc;

#[derive(Debug, Clone)]
pub enum FileUploadError {
    ServerError,
    ServerConfigError,
    UploadError,
}

/// Event to trigger when we want to upload a file to a nip96 server
#[derive(Event)]
pub struct FileUploadReqEvent {
    pub file_data: Vec<u8>,
    // Upload ID
    pub id: String,
}

/// Message written after a file upload
#[derive(Message)]
pub struct FileUploadSuccess {
    // URL of the file we uploaded
    pub url: Url,
    // Upload ID
    pub id: String,
}

/// Upload a file to a nip96 server using ehttp
pub async fn upload_file(
    data: Vec<u8>,
    srv_url: Option<Url>,
    signer: Arc<dyn NostrSigner>,
) -> Result<Url, FileUploadError> {
    let server_url =
        srv_url.unwrap_or(Url::parse("https://nostr.build").unwrap());

    let config_url = nip96::get_server_config_url(&server_url)
        .map_err(|_| FileUploadError::ServerConfigError)?;

    let Ok(resp) = ehttp::fetch_async(ehttp::Request::get(config_url)).await
    else {
        return Err(FileUploadError::ServerConfigError);
    };
    let Ok(config) = resp.json::<nip96::ServerConfig>() else {
        return Err(FileUploadError::ServerConfigError);
    };

    let upload_req = nip96::UploadRequest::new(&signer, &config, &data)
        .await
        .map_err(|_| FileUploadError::ServerError)?;

    let mut request = ehttp::Request::multipart(
        upload_req.url,
        MultipartBuilder::new()
            .add_stream(&mut Cursor::new(data), "file", Some("file"), None)
            .unwrap(),
    );

    request
        .headers
        .insert("Authorization", upload_req.authorization);

    let Ok(resp) = ehttp::fetch_async(request).await else {
        return Err(FileUploadError::UploadError);
    };
    let Ok(upload_resp) = resp.json::<nip96::UploadResponse>() else {
        return Err(FileUploadError::UploadError);
    };

    match upload_resp.status {
        nip96::UploadResponseStatus::Success => {
            let url = upload_resp
                .download_url()
                .map_err(|_| FileUploadError::UploadError)?;

            Ok(url.clone())
        }
        nip96::UploadResponseStatus::Error => Err(FileUploadError::UploadError),
    }
}