feat: group metadata query
This commit is contained in:
3
src-tauri/Cargo.lock
generated
3
src-tauri/Cargo.lock
generated
@@ -6079,8 +6079,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "tauri-plugin-store"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5058f179f7215390fc5a68eeffcb805b7e2681d6e817a5d08094fae7ab649e68"
|
||||
source = "git+https://github.com/tauri-apps/plugins-workspace?rev=8c67d44#8c67d44aef60b1427019538d8420787ef35bd3d5"
|
||||
dependencies = [
|
||||
"dunce",
|
||||
"log",
|
||||
|
||||
@@ -27,7 +27,7 @@ tauri-plugin-process = "2.0.0"
|
||||
tauri-plugin-shell = "2.0.0"
|
||||
tauri-plugin-updater = "2.0.0"
|
||||
tauri-plugin-upload = "2.0.0"
|
||||
tauri-plugin-store = "2.0.0"
|
||||
tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace", rev = "8c67d44" }
|
||||
tauri-plugin-prevent-default = "0.6"
|
||||
tauri-plugin-theme = "2.1.2"
|
||||
tauri-plugin-decorum = { git = "https://github.com/clearlysid/tauri-plugin-decorum" }
|
||||
|
||||
@@ -17,15 +17,6 @@
|
||||
"core:resources:default",
|
||||
"core:menu:default",
|
||||
"core:tray:default",
|
||||
"notification:allow-is-permission-granted",
|
||||
"notification:allow-request-permission",
|
||||
"notification:default",
|
||||
"os:allow-locale",
|
||||
"os:allow-platform",
|
||||
"os:allow-os-type",
|
||||
"updater:default",
|
||||
"updater:allow-check",
|
||||
"updater:allow-download-and-install",
|
||||
"core:window:allow-create",
|
||||
"core:window:allow-close",
|
||||
"core:window:allow-destroy",
|
||||
@@ -36,22 +27,31 @@
|
||||
"core:window:allow-set-size",
|
||||
"core:window:allow-start-dragging",
|
||||
"core:window:allow-toggle-maximize",
|
||||
"decorum:allow-show-snap-overlay",
|
||||
"clipboard-manager:allow-write-text",
|
||||
"clipboard-manager:allow-read-text",
|
||||
"core:webview:allow-create-webview-window",
|
||||
"core:webview:allow-create-webview",
|
||||
"core:webview:allow-set-webview-size",
|
||||
"core:webview:allow-set-webview-position",
|
||||
"core:webview:allow-webview-close",
|
||||
"core:menu:allow-new",
|
||||
"core:menu:allow-popup",
|
||||
"notification:allow-is-permission-granted",
|
||||
"notification:allow-request-permission",
|
||||
"notification:default",
|
||||
"os:allow-locale",
|
||||
"os:allow-platform",
|
||||
"os:allow-os-type",
|
||||
"updater:default",
|
||||
"updater:allow-check",
|
||||
"updater:allow-download-and-install",
|
||||
"decorum:allow-show-snap-overlay",
|
||||
"clipboard-manager:allow-write-text",
|
||||
"clipboard-manager:allow-read-text",
|
||||
"dialog:allow-open",
|
||||
"dialog:allow-ask",
|
||||
"dialog:allow-message",
|
||||
"process:allow-restart",
|
||||
"process:allow-exit",
|
||||
"fs:allow-read-file",
|
||||
"core:menu:allow-new",
|
||||
"core:menu:allow-popup",
|
||||
"shell:allow-open",
|
||||
"store:default",
|
||||
"prevent-default:default",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
{"window":{"identifier":"window","description":"Capability for the desktop","local":true,"windows":["*"],"permissions":["core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","notification:allow-is-permission-granted","notification:allow-request-permission","notification:default","os:allow-locale","os:allow-platform","os:allow-os-type","updater:default","updater:allow-check","updater:allow-download-and-install","core:window:allow-create","core:window:allow-close","core:window:allow-destroy","core:window:allow-set-focus","core:window:allow-center","core:window:allow-minimize","core:window:allow-maximize","core:window:allow-set-size","core:window:allow-start-dragging","core:window:allow-toggle-maximize","decorum:allow-show-snap-overlay","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","core:webview:allow-create-webview-window","core:webview:allow-create-webview","core:webview:allow-set-webview-size","core:webview:allow-set-webview-position","core:webview:allow-webview-close","dialog:allow-open","dialog:allow-ask","dialog:allow-message","process:allow-restart","process:allow-exit","fs:allow-read-file","core:menu:allow-new","core:menu:allow-popup","shell:allow-open","store:default","prevent-default:default","theme:default",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"},{"path":"$RESOURCE/resources/*"}]}],"platforms":["macOS","windows"]}}
|
||||
{"window":{"identifier":"window","description":"Capability for the desktop","local":true,"windows":["*"],"permissions":["core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","core:window:allow-create","core:window:allow-close","core:window:allow-destroy","core:window:allow-set-focus","core:window:allow-center","core:window:allow-minimize","core:window:allow-maximize","core:window:allow-set-size","core:window:allow-start-dragging","core:window:allow-toggle-maximize","core:webview:allow-create-webview-window","core:webview:allow-create-webview","core:webview:allow-set-webview-size","core:webview:allow-set-webview-position","core:webview:allow-webview-close","core:menu:allow-new","core:menu:allow-popup","notification:allow-is-permission-granted","notification:allow-request-permission","notification:default","os:allow-locale","os:allow-platform","os:allow-os-type","updater:default","updater:allow-check","updater:allow-download-and-install","decorum:allow-show-snap-overlay","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","dialog:allow-open","dialog:allow-ask","dialog:allow-message","process:allow-restart","process:allow-exit","fs:allow-read-file","shell:allow-open","store:default","prevent-default:default","theme:default",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"},{"path":"$RESOURCE/resources/*"}]}],"platforms":["macOS","windows"]}}
|
||||
@@ -5444,11 +5444,6 @@
|
||||
"type": "string",
|
||||
"const": "store:allow-clear"
|
||||
},
|
||||
{
|
||||
"description": "Enables the create_store command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:allow-create-store"
|
||||
},
|
||||
{
|
||||
"description": "Enables the delete command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -5464,6 +5459,11 @@
|
||||
"type": "string",
|
||||
"const": "store:allow-get"
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_store command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:allow-get-store"
|
||||
},
|
||||
{
|
||||
"description": "Enables the has command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -5484,6 +5484,11 @@
|
||||
"type": "string",
|
||||
"const": "store:allow-load"
|
||||
},
|
||||
{
|
||||
"description": "Enables the reload command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:allow-reload"
|
||||
},
|
||||
{
|
||||
"description": "Enables the reset command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -5509,11 +5514,6 @@
|
||||
"type": "string",
|
||||
"const": "store:deny-clear"
|
||||
},
|
||||
{
|
||||
"description": "Denies the create_store command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:deny-create-store"
|
||||
},
|
||||
{
|
||||
"description": "Denies the delete command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -5529,6 +5529,11 @@
|
||||
"type": "string",
|
||||
"const": "store:deny-get"
|
||||
},
|
||||
{
|
||||
"description": "Denies the get_store command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:deny-get-store"
|
||||
},
|
||||
{
|
||||
"description": "Denies the has command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -5549,6 +5554,11 @@
|
||||
"type": "string",
|
||||
"const": "store:deny-load"
|
||||
},
|
||||
{
|
||||
"description": "Denies the reload command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:deny-reload"
|
||||
},
|
||||
{
|
||||
"description": "Denies the reset command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
|
||||
@@ -5444,11 +5444,6 @@
|
||||
"type": "string",
|
||||
"const": "store:allow-clear"
|
||||
},
|
||||
{
|
||||
"description": "Enables the create_store command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:allow-create-store"
|
||||
},
|
||||
{
|
||||
"description": "Enables the delete command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -5464,6 +5459,11 @@
|
||||
"type": "string",
|
||||
"const": "store:allow-get"
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_store command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:allow-get-store"
|
||||
},
|
||||
{
|
||||
"description": "Enables the has command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -5484,6 +5484,11 @@
|
||||
"type": "string",
|
||||
"const": "store:allow-load"
|
||||
},
|
||||
{
|
||||
"description": "Enables the reload command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:allow-reload"
|
||||
},
|
||||
{
|
||||
"description": "Enables the reset command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -5509,11 +5514,6 @@
|
||||
"type": "string",
|
||||
"const": "store:deny-clear"
|
||||
},
|
||||
{
|
||||
"description": "Denies the create_store command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:deny-create-store"
|
||||
},
|
||||
{
|
||||
"description": "Denies the delete command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -5529,6 +5529,11 @@
|
||||
"type": "string",
|
||||
"const": "store:deny-get"
|
||||
},
|
||||
{
|
||||
"description": "Denies the get_store command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:deny-get-store"
|
||||
},
|
||||
{
|
||||
"description": "Denies the has command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -5549,6 +5554,11 @@
|
||||
"type": "string",
|
||||
"const": "store:deny-load"
|
||||
},
|
||||
{
|
||||
"description": "Denies the reload command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:deny-reload"
|
||||
},
|
||||
{
|
||||
"description": "Denies the reset command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
|
||||
@@ -32,41 +32,28 @@ pub struct Mention {
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn get_profile(
|
||||
id: String,
|
||||
cache_only: bool,
|
||||
state: State<'_, Nostr>,
|
||||
) -> Result<String, String> {
|
||||
pub async fn get_profile(id: String, state: State<'_, Nostr>) -> Result<String, String> {
|
||||
let client = &state.client;
|
||||
let public_key = PublicKey::parse(&id).map_err(|e| e.to_string())?;
|
||||
|
||||
if cache_only {
|
||||
let profile = client
|
||||
.database()
|
||||
.profile(public_key)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
return Ok(profile.metadata().as_json());
|
||||
};
|
||||
|
||||
let filter = Filter::new()
|
||||
.author(public_key)
|
||||
.kind(Kind::Metadata)
|
||||
.limit(1);
|
||||
|
||||
let mut metadata = Metadata::new();
|
||||
|
||||
let mut rx = client
|
||||
.stream_events(vec![filter], Some(Duration::from_secs(5)))
|
||||
let events = client
|
||||
.database()
|
||||
.query(vec![filter])
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
while let Some(event) = rx.next().await {
|
||||
metadata = Metadata::from_json(&event.content).map_err(|e| e.to_string())?;
|
||||
match events.first() {
|
||||
Some(event) => match Metadata::from_json(&event.content) {
|
||||
Ok(metadata) => Ok(metadata.as_json()),
|
||||
Err(e) => Err(e.to_string()),
|
||||
},
|
||||
None => Err("Metadata not found".into()),
|
||||
}
|
||||
|
||||
Ok(metadata.as_json())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -627,15 +614,15 @@ pub async fn get_notifications(id: String, state: State<'_, Nostr>) -> Result<Ve
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn get_user_settings(state: State<'_, Nostr>) -> Result<Settings, String> {
|
||||
Ok(state.settings.lock().unwrap().clone())
|
||||
pub async fn get_user_settings(state: State<'_, Nostr>) -> Result<Settings, String> {
|
||||
Ok(state.settings.lock().await.clone())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn set_user_settings(settings: String, state: State<'_, Nostr>) -> Result<(), String> {
|
||||
let parsed: Settings = serde_json::from_str(&settings).map_err(|e| e.to_string())?;
|
||||
state.settings.lock().unwrap().clone_from(&parsed);
|
||||
state.settings.lock().await.clone_from(&parsed);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -12,25 +12,32 @@ use serde::{Deserialize, Serialize};
|
||||
use specta::Type;
|
||||
use specta_typescript::Typescript;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fs,
|
||||
io::{self, BufRead},
|
||||
str::FromStr,
|
||||
sync::Mutex,
|
||||
time::Duration,
|
||||
};
|
||||
use tauri::{path::BaseDirectory, Emitter, EventTarget, Manager};
|
||||
use tauri::{path::BaseDirectory, Emitter, EventTarget, Listener, Manager};
|
||||
use tauri_plugin_decorum::WebviewWindowExt;
|
||||
use tauri_plugin_notification::{NotificationExt, PermissionState};
|
||||
use tauri_specta::{collect_commands, Builder};
|
||||
use tokio::{sync::Mutex, sync::RwLock, time::sleep};
|
||||
|
||||
pub mod commands;
|
||||
pub mod common;
|
||||
|
||||
pub struct Nostr {
|
||||
client: Client,
|
||||
queue: RwLock<HashSet<PublicKey>>,
|
||||
settings: Mutex<Settings>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Payload {
|
||||
id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Type)]
|
||||
pub struct Settings {
|
||||
proxy: Option<String>,
|
||||
@@ -146,6 +153,7 @@ fn main() {
|
||||
.setup(move |app| {
|
||||
let handle = app.handle();
|
||||
let handle_clone = handle.clone();
|
||||
let handle_clone_child = handle_clone.clone();
|
||||
let main_window = app.get_webview_window("main").unwrap();
|
||||
|
||||
let config_dir = handle
|
||||
@@ -174,7 +182,7 @@ fn main() {
|
||||
|
||||
// Config
|
||||
let opts = Options::new()
|
||||
.gossip(false)
|
||||
.gossip(true)
|
||||
.max_avg_latency(Duration::from_millis(300))
|
||||
.automatic_authentication(true)
|
||||
.connection_timeout(Some(Duration::from_secs(5)))
|
||||
@@ -229,9 +237,40 @@ fn main() {
|
||||
// Create global state
|
||||
app.manage(Nostr {
|
||||
client,
|
||||
queue: RwLock::new(HashSet::new()),
|
||||
settings: Mutex::new(Settings::default()),
|
||||
});
|
||||
|
||||
// Listen for request metadata
|
||||
app.listen_any("request_metadata", move |event| {
|
||||
let payload = event.payload();
|
||||
let parsed_payload: Payload = serde_json::from_str(payload).expect("Parse failed");
|
||||
let handle = handle_clone_child.clone();
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let state = handle.state::<Nostr>();
|
||||
let client = &state.client;
|
||||
|
||||
if let Ok(public_key) = PublicKey::parse(parsed_payload.id) {
|
||||
let mut write_queue = state.queue.write().await;
|
||||
write_queue.insert(public_key);
|
||||
}
|
||||
|
||||
sleep(Duration::from_millis(300)).await;
|
||||
|
||||
let read_queue = state.queue.read().await;
|
||||
let authors: Vec<PublicKey> = read_queue.iter().copied().collect();
|
||||
let filter = Filter::new().authors(authors).kind(Kind::Metadata);
|
||||
let opts = SubscribeAutoCloseOptions::default()
|
||||
.filter(FilterOptions::WaitDurationAfterEOSE(Duration::from_secs(3)));
|
||||
|
||||
if client.subscribe(vec![filter], Some(opts)).await.is_ok() {
|
||||
let mut write_queue = state.queue.write().await;
|
||||
write_queue.clear();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Run notification thread
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let state = handle_clone.state::<Nostr>();
|
||||
@@ -278,46 +317,52 @@ fn main() {
|
||||
|
||||
let _ = client
|
||||
.handle_notifications(|notification| async {
|
||||
if let RelayPoolNotification::Event {
|
||||
event,
|
||||
subscription_id,
|
||||
..
|
||||
} = notification
|
||||
{
|
||||
// Handle events from notification subscription
|
||||
if subscription_id == notification_id {
|
||||
// Send native notification
|
||||
if allow_notification {
|
||||
let author = client
|
||||
.database()
|
||||
.profile(event.pubkey)
|
||||
.await
|
||||
.unwrap_or_else(|_| {
|
||||
DatabaseProfile::new(event.pubkey, Metadata::new())
|
||||
});
|
||||
#[allow(clippy::collapsible_match)]
|
||||
if let RelayPoolNotification::Message { message, .. } = notification {
|
||||
if let RelayMessage::Event {
|
||||
event,
|
||||
subscription_id,
|
||||
..
|
||||
} = message
|
||||
{
|
||||
if subscription_id == notification_id {
|
||||
// Send native notification
|
||||
if allow_notification {
|
||||
let author = client
|
||||
.database()
|
||||
.profile(event.pubkey)
|
||||
.await
|
||||
.unwrap_or_else(|_| {
|
||||
DatabaseProfile::new(event.pubkey, Metadata::new())
|
||||
});
|
||||
|
||||
send_event_notification(
|
||||
&event,
|
||||
author.metadata(),
|
||||
&handle_clone,
|
||||
);
|
||||
}
|
||||
} else if event.kind != Kind::RelayList {
|
||||
let payload = RichEvent {
|
||||
raw: event.as_json(),
|
||||
parsed: if event.kind == Kind::TextNote {
|
||||
Some(parse_event(&event.content).await)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
};
|
||||
send_event_notification(
|
||||
&event,
|
||||
author.metadata(),
|
||||
&handle_clone,
|
||||
);
|
||||
}
|
||||
} else if event.kind == Kind::Metadata {
|
||||
if let Err(e) = handle_clone.emit("metadata", event.as_json()) {
|
||||
println!("Emitter error: {}", e)
|
||||
}
|
||||
} else if event.kind != Kind::RelayList {
|
||||
let payload = RichEvent {
|
||||
raw: event.as_json(),
|
||||
parsed: if event.kind == Kind::TextNote {
|
||||
Some(parse_event(&event.content).await)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
};
|
||||
|
||||
if let Err(e) = handle_clone.emit_to(
|
||||
EventTarget::labeled(subscription_id.to_string()),
|
||||
"event",
|
||||
payload,
|
||||
) {
|
||||
println!("Emitter error: {}", e)
|
||||
if let Err(e) = handle_clone.emit_to(
|
||||
EventTarget::labeled(subscription_id.to_string()),
|
||||
"event",
|
||||
payload,
|
||||
) {
|
||||
println!("Emitter error: {}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user