chore: update gpui and nostr-sdk
This commit is contained in:
@@ -51,7 +51,7 @@ impl Drop for MacOsUnmounter {
|
||||
);
|
||||
}
|
||||
Err(error) => {
|
||||
log::error!("Error while trying to unmount disk image: {:?}", error);
|
||||
log::error!("Error while trying to unmount disk image: {error:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,7 +154,7 @@ impl AutoUpdater {
|
||||
target_file.write_all(&chunk).await?;
|
||||
}
|
||||
|
||||
log::info!("downloaded update. path:{:?}", downloaded_asset);
|
||||
log::info!("downloaded update. path: {downloaded_asset:?}");
|
||||
|
||||
Ok((temp_dir, downloaded_asset))
|
||||
} else {
|
||||
@@ -271,7 +271,7 @@ impl AutoUpdater {
|
||||
let from = extracted.join(&app_folder_name);
|
||||
let mut to = home_dir.join(".local");
|
||||
|
||||
let expected_suffix = format!("{}/libexec/coop", app_folder_name);
|
||||
let expected_suffix = format!("{app_folder_name}/libexec/coop");
|
||||
|
||||
if let Some(prefix) = running_app_path
|
||||
.to_str()
|
||||
|
||||
@@ -2,48 +2,17 @@ use std::collections::HashSet;
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{anyhow, Error, Result};
|
||||
use gpui::{Image, ImageFormat};
|
||||
use itertools::Itertools;
|
||||
use nostr_sdk::prelude::*;
|
||||
use qrcode_generator::QrCodeEcc;
|
||||
use reqwest::Client as ReqClient;
|
||||
|
||||
pub mod debounced_delay;
|
||||
pub mod handle_auth;
|
||||
pub mod nip05;
|
||||
pub mod nip96;
|
||||
pub mod profile;
|
||||
|
||||
pub async fn verify_nip05(public_key: PublicKey, address: &str) -> Result<bool, Error> {
|
||||
let req_client = ReqClient::new();
|
||||
let address = Nip05Address::parse(address)?;
|
||||
let res = req_client.get(address.url().to_string()).send().await?;
|
||||
let json: Value = res.json().await?;
|
||||
let verify = nip05::verify_from_json(&public_key, &address, &json);
|
||||
|
||||
Ok(verify)
|
||||
}
|
||||
|
||||
pub async fn nip05_profile(address: &str) -> Result<Nip05Profile, Error> {
|
||||
let req_client = ReqClient::new();
|
||||
let address = Nip05Address::parse(address)?;
|
||||
let res = req_client.get(address.url().to_string()).send().await?;
|
||||
let json: Value = res.json().await?;
|
||||
|
||||
if let Ok(profile) = Nip05Profile::from_json(&address, &json) {
|
||||
Ok(profile)
|
||||
} else {
|
||||
Err(anyhow!("Failed to get NIP-05 profile"))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn nip96_upload(client: &Client, server: Url, file: Vec<u8>) -> Result<Url, Error> {
|
||||
let signer = client.signer().await?;
|
||||
let config = nip96::get_server_config(server.to_owned(), None).await?;
|
||||
let url = nip96::upload_data(&signer, &config, file, None, None).await?;
|
||||
|
||||
Ok(url)
|
||||
}
|
||||
|
||||
pub fn room_hash(event: &Event) -> u64 {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
let mut pubkeys: Vec<PublicKey> = vec![];
|
||||
|
||||
31
crates/common/src/nip05.rs
Normal file
31
crates/common/src/nip05.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use anyhow::anyhow;
|
||||
use nostr::prelude::*;
|
||||
use reqwest::Client as ReqClient;
|
||||
|
||||
pub async fn nip05_verify(public_key: PublicKey, address: &str) -> Result<bool, anyhow::Error> {
|
||||
let req_client = ReqClient::new();
|
||||
let address = Nip05Address::parse(address)?;
|
||||
|
||||
// Get NIP-05 response
|
||||
let res = req_client.get(address.url().to_string()).send().await?;
|
||||
let json: Value = res.json().await?;
|
||||
|
||||
let verify = nip05::verify_from_json(&public_key, &address, &json);
|
||||
|
||||
Ok(verify)
|
||||
}
|
||||
|
||||
pub async fn nip05_profile(address: &str) -> Result<Nip05Profile, anyhow::Error> {
|
||||
let req_client = ReqClient::new();
|
||||
let address = Nip05Address::parse(address)?;
|
||||
|
||||
// Get NIP-05 response
|
||||
let res = req_client.get(address.url().to_string()).send().await?;
|
||||
let json: Value = res.json().await?;
|
||||
|
||||
if let Ok(profile) = Nip05Profile::from_json(&address, &json) {
|
||||
Ok(profile)
|
||||
} else {
|
||||
Err(anyhow!("Failed to get NIP-05 profile"))
|
||||
}
|
||||
}
|
||||
82
crates/common/src/nip96.rs
Normal file
82
crates/common/src/nip96.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use anyhow::anyhow;
|
||||
use nostr::hashes::sha256::Hash as Sha256Hash;
|
||||
use nostr::hashes::Hash;
|
||||
use nostr::prelude::*;
|
||||
use nostr_sdk::prelude::*;
|
||||
use reqwest::multipart;
|
||||
use reqwest::Client as ReqClient;
|
||||
use reqwest::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 nip96_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().await?;
|
||||
|
||||
let url = upload(&signer, &config, file, None).await?;
|
||||
|
||||
Ok(url)
|
||||
}
|
||||
@@ -18,8 +18,7 @@ impl RenderProfile for Profile {
|
||||
.map(|picture| {
|
||||
if proxy {
|
||||
format!(
|
||||
"{}/?url={}&w=100&h=100&fit=cover&mask=circle&default={}&n=-1",
|
||||
IMAGE_RESIZE_SERVICE, picture, FALLBACK_IMG
|
||||
"{IMAGE_RESIZE_SERVICE}/?url={picture}&w=100&h=100&fit=cover&mask=circle&default={FALLBACK_IMG}&n=-1"
|
||||
)
|
||||
.into()
|
||||
} else {
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::sync::Arc;
|
||||
|
||||
use chats::message::Message;
|
||||
use chats::room::{Room, RoomKind, SendError};
|
||||
use common::nip96_upload;
|
||||
use common::nip96::nip96_upload;
|
||||
use common::profile::RenderProfile;
|
||||
use global::shared_state;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
@@ -390,16 +390,9 @@ impl Chat {
|
||||
|
||||
// Spawn task via async utility instead of GPUI context
|
||||
nostr_sdk::async_utility::task::spawn(async move {
|
||||
let url = match nip96_upload(shared_state().client(), nip96, file_data)
|
||||
let url = nip96_upload(shared_state().client(), &nip96, file_data)
|
||||
.await
|
||||
{
|
||||
Ok(url) => Some(url),
|
||||
Err(e) => {
|
||||
log::error!("Upload error: {e}");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
.ok();
|
||||
_ = tx.send(url);
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::time::Duration;
|
||||
use anyhow::{anyhow, Error};
|
||||
use chats::room::{Room, RoomKind};
|
||||
use chats::ChatRegistry;
|
||||
use common::nip05_profile;
|
||||
use common::nip05::nip05_profile;
|
||||
use common::profile::RenderProfile;
|
||||
use global::shared_state;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
|
||||
@@ -592,8 +592,7 @@ impl Render for Login {
|
||||
.text_center()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child(SharedString::from(format!(
|
||||
"Approve connection request from your signer in {} seconds",
|
||||
i
|
||||
"Approve connection request from your signer in {i} seconds"
|
||||
))),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use common::nip96_upload;
|
||||
use common::nip96::nip96_upload;
|
||||
use global::shared_state;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
@@ -158,7 +158,7 @@ impl NewAccount {
|
||||
|
||||
nostr_sdk::async_utility::task::spawn(async move {
|
||||
if let Ok(url) =
|
||||
nip96_upload(shared_state().client(), nip96, file_data).await
|
||||
nip96_upload(shared_state().client(), &nip96, file_data).await
|
||||
{
|
||||
_ = tx.send(url);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
use common::nip96_upload;
|
||||
use common::nip96::nip96_upload;
|
||||
use global::shared_state;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
@@ -104,7 +104,7 @@ impl Profile {
|
||||
}
|
||||
|
||||
fn upload(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let nip96 = AppSettings::get_global(cx).settings.media_server.clone();
|
||||
let nip96_server = AppSettings::get_global(cx).settings.media_server.clone();
|
||||
let avatar_input = self.avatar_input.downgrade();
|
||||
let paths = cx.prompt_for_paths(PathPromptOptions {
|
||||
files: true,
|
||||
@@ -125,7 +125,8 @@ impl Profile {
|
||||
|
||||
nostr_sdk::async_utility::task::spawn(async move {
|
||||
if let Ok(url) =
|
||||
nip96_upload(shared_state().client(), nip96, file_data).await
|
||||
nip96_upload(shared_state().client(), &nip96_server, file_data)
|
||||
.await
|
||||
{
|
||||
_ = tx.send(url);
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ impl Relays {
|
||||
]);
|
||||
|
||||
if let Err(e) = client.send_event_builder(builder).await {
|
||||
log::error!("Failed to send relay list event: {}", e);
|
||||
log::error!("Failed to send relay list event: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ impl Relays {
|
||||
)
|
||||
.await
|
||||
{
|
||||
log::error!("Failed to subscribe to new messages: {}", e);
|
||||
log::error!("Failed to subscribe to new messages: {e}");
|
||||
}
|
||||
|
||||
Ok(output.val)
|
||||
|
||||
@@ -6,8 +6,8 @@ use anyhow::Error;
|
||||
use chats::room::{Room, RoomKind};
|
||||
use chats::{ChatRegistry, RoomEmitter};
|
||||
use common::debounced_delay::DebouncedDelay;
|
||||
use common::nip05::nip05_verify;
|
||||
use common::profile::RenderProfile;
|
||||
use common::verify_nip05;
|
||||
use element::DisplayRoom;
|
||||
use global::constants::{DEFAULT_MODAL_WIDTH, SEARCH_RELAYS};
|
||||
use global::shared_state;
|
||||
@@ -185,7 +185,7 @@ impl Sidebar {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !verify_nip05(event.pubkey, target).await.unwrap_or(false) {
|
||||
if !nip05_verify(event.pubkey, target).await.unwrap_or(false) {
|
||||
// Skip if NIP-05 is not valid or failed to verify
|
||||
continue;
|
||||
};
|
||||
@@ -218,7 +218,7 @@ impl Sidebar {
|
||||
this.update(cx, |this, cx| {
|
||||
if result.is_empty() {
|
||||
let msg =
|
||||
format!("There are no users matching query {}", query_cloned);
|
||||
format!("There are no users matching query {query_cloned}");
|
||||
window.push_notification(Notification::info(msg), cx);
|
||||
this.set_finding(false, cx);
|
||||
} else {
|
||||
|
||||
@@ -369,13 +369,13 @@ impl Globals {
|
||||
pub(crate) async fn connect(&self) {
|
||||
for relay in BOOTSTRAP_RELAYS.into_iter() {
|
||||
if let Err(e) = self.client.add_relay(relay).await {
|
||||
log::error!("Failed to add relay {}: {}", relay, e);
|
||||
log::error!("Failed to add relay {relay}: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
for relay in SEARCH_RELAYS.into_iter() {
|
||||
if let Err(e) = self.client.add_relay(relay).await {
|
||||
log::error!("Failed to add relay {}: {}", relay, e);
|
||||
log::error!("Failed to add relay {relay}: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,7 +470,7 @@ impl Globals {
|
||||
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts))
|
||||
.await
|
||||
{
|
||||
log::error!("Failed to subscribe for app updates: {}", e);
|
||||
log::error!("Failed to subscribe for app updates: {e}");
|
||||
}
|
||||
|
||||
log::info!("Subscribed to app updates");
|
||||
@@ -623,7 +623,7 @@ impl Globals {
|
||||
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts))
|
||||
.await
|
||||
{
|
||||
log::error!("Failed to subscribe for file metadata: {}", e);
|
||||
log::error!("Failed to subscribe for file metadata: {e}");
|
||||
} else {
|
||||
self.send_signal(NostrSignal::AppUpdate(event.to_owned()))
|
||||
.await;
|
||||
|
||||
@@ -408,7 +408,7 @@ impl Identity {
|
||||
);
|
||||
|
||||
if let Err(e) = client.send_event_builder(builder).await {
|
||||
log::error!("Failed to send relay list event: {}", e);
|
||||
log::error!("Failed to send relay list event: {e}");
|
||||
};
|
||||
|
||||
// Create messaging relay list
|
||||
@@ -423,7 +423,7 @@ impl Identity {
|
||||
);
|
||||
|
||||
if let Err(e) = client.send_event_builder(builder).await {
|
||||
log::error!("Failed to send messaging relay list event: {}", e);
|
||||
log::error!("Failed to send messaging relay list event: {e}");
|
||||
};
|
||||
|
||||
// Subscribe for user's data
|
||||
|
||||
@@ -518,7 +518,7 @@ impl TabPanel {
|
||||
};
|
||||
|
||||
Some(
|
||||
Button::new(SharedString::from(format!("toggle-dock:{:?}", placement)))
|
||||
Button::new(SharedString::from(format!("toggle-dock:{placement:?}")))
|
||||
.icon(icon)
|
||||
.small()
|
||||
.ghost()
|
||||
|
||||
@@ -102,11 +102,11 @@ impl RenderOnce for EmojiPicker {
|
||||
input.update(cx, |this, cx| {
|
||||
let current = this.value();
|
||||
let new_text = if current.is_empty() {
|
||||
format!("{}", item)
|
||||
format!("{item}")
|
||||
} else if current.ends_with(" ") {
|
||||
format!("{}{}", current, item)
|
||||
format!("{current}{item}")
|
||||
} else {
|
||||
format!("{} {}", current, item)
|
||||
format!("{current} {item}")
|
||||
};
|
||||
this.set_value(new_text, window, cx);
|
||||
});
|
||||
|
||||
@@ -302,7 +302,7 @@ impl MaskPattern {
|
||||
if fraction == &Some(0) {
|
||||
int_with_sep
|
||||
} else {
|
||||
format!("{}.{}", int_with_sep, frac)
|
||||
format!("{int_with_sep}.{frac}")
|
||||
}
|
||||
} else {
|
||||
int_with_sep
|
||||
|
||||
@@ -50,7 +50,7 @@ pub trait PopupMenuExt: Styled + Selectable + IntoElement + 'static {
|
||||
let style = self.style().clone();
|
||||
let element_id = self.element_id();
|
||||
|
||||
Popover::new(SharedString::from(format!("popup-menu:{:?}", element_id)))
|
||||
Popover::new(SharedString::from(format!("popup-menu:{element_id:?}")))
|
||||
.no_style()
|
||||
.trigger(self)
|
||||
.trigger_style(style)
|
||||
@@ -716,7 +716,7 @@ impl Render for PopupMenu {
|
||||
/// Return the Platform specific keybinding string by KeyStroke
|
||||
pub fn key_shortcut(key: Keystroke) -> String {
|
||||
if cfg!(target_os = "macos") {
|
||||
return format!("{}", key);
|
||||
return format!("{key}");
|
||||
}
|
||||
|
||||
let mut parts = vec![];
|
||||
|
||||
@@ -293,7 +293,7 @@ pub fn render_plain_text_mut(
|
||||
|
||||
// Make it clickable
|
||||
link_ranges.push(new_range);
|
||||
link_urls.push(format!("mention:{}", entity_without_prefix));
|
||||
link_urls.push(format!("mention:{entity_without_prefix}"));
|
||||
|
||||
// Adjust subsequent ranges if needed
|
||||
if length_diff != 0 {
|
||||
@@ -301,11 +301,11 @@ pub fn render_plain_text_mut(
|
||||
}
|
||||
} else {
|
||||
// No profile match or not a profile entity - create njump.me link
|
||||
let njump_url = format!("https://njump.me/{}", entity_without_prefix);
|
||||
let njump_url = format!("https://njump.me/{entity_without_prefix}");
|
||||
|
||||
// Create a shortened display format for the URL
|
||||
let shortened_entity = format_shortened_entity(entity_without_prefix);
|
||||
let display_text = format!("https://njump.me/{}", shortened_entity);
|
||||
let display_text = format!("https://njump.me/{shortened_entity}");
|
||||
|
||||
// Replace the original entity with the shortened display version
|
||||
text.replace_range(range.clone(), &display_text);
|
||||
@@ -350,7 +350,7 @@ fn format_shortened_entity(entity: &str) -> String {
|
||||
let prefix = &entity[0..=prefix_end]; // Include the '1'
|
||||
let suffix = &entity[entity.len() - 4..]; // Last 4 chars
|
||||
|
||||
format!("{}...{}", prefix, suffix)
|
||||
format!("{prefix}...{suffix}")
|
||||
} else {
|
||||
entity.to_string()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user