Some improments and Negentropy (#219)

* feat: adjust default window size

* feat: save window state

* feat: add window state plugin

* feat: add search

* feat: use negentropy for newsfeed

* feat: live feeds

* feat: add search user
This commit is contained in:
雨宮蓮
2024-06-30 14:26:02 +07:00
committed by GitHub
parent 968b1ada94
commit 0fec21b9ce
46 changed files with 5633 additions and 3938 deletions

View File

@@ -71,6 +71,10 @@ impl Default for Settings {
}
}
pub const FETCH_LIMIT: usize = 20;
pub const NEWSFEED_NEG_LIMIT: usize = 256;
pub const NOTIFICATION_NEG_LIMIT: usize = 64;
fn main() {
let mut ctx = tauri::generate_context!();
@@ -113,9 +117,9 @@ fn main() {
nostr::event::get_event_from,
nostr::event::get_replies,
nostr::event::listen_event_reply,
nostr::event::unlisten_event_reply,
nostr::event::get_events_by,
nostr::event::get_local_events,
nostr::event::listen_local_event,
nostr::event::get_group_events,
nostr::event::get_global_events,
nostr::event::get_hashtag_events,
@@ -124,6 +128,7 @@ fn main() {
nostr::event::repost,
nostr::event::event_to_bech32,
nostr::event::user_to_bech32,
nostr::event::unlisten,
commands::folder::show_in_folder,
commands::window::create_column,
commands::window::close_column,
@@ -263,6 +268,11 @@ fn main() {
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_upload::init())
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(
tauri_plugin_window_state::Builder::new()
.with_denylist(&["panel"])
.build(),
)
.invoke_handler(invoke_handler)
.build(ctx)
.expect("error while running tauri application")

View File

@@ -7,7 +7,7 @@ use specta::Type;
use tauri::State;
use crate::nostr::utils::{create_event_tags, dedup_event, parse_event, Meta};
use crate::Nostr;
use crate::{Nostr, FETCH_LIMIT};
#[derive(Debug, Clone, Serialize, Type)]
pub struct RichEvent {
@@ -197,18 +197,6 @@ pub async fn listen_event_reply(id: &str, state: State<'_, Nostr>) -> Result<(),
Ok(())
}
#[tauri::command]
#[specta::specta]
pub async fn unlisten_event_reply(id: &str, state: State<'_, Nostr>) -> Result<(), ()> {
let client = &state.client;
let sub_id = SubscriptionId::new(id);
// Remove subscription
client.unsubscribe(sub_id).await;
Ok(())
}
#[tauri::command]
#[specta::specta]
pub async fn get_events_by(
@@ -227,7 +215,7 @@ pub async fn get_events_by(
let filter = Filter::new()
.kinds(vec![Kind::TextNote, Kind::Repost])
.author(author)
.limit(20)
.limit(FETCH_LIMIT)
.until(until);
match client.get_events_of(vec![filter], None).await {
@@ -275,17 +263,13 @@ pub async fn get_local_events(
let filter = Filter::new()
.kinds(vec![Kind::TextNote, Kind::Repost])
.limit(20)
.limit(64)
.until(as_of)
.authors(authors);
match client
.get_events_of(vec![filter], Some(Duration::from_secs(10)))
.await
{
match client.database().query(vec![filter], Order::Desc).await {
Ok(events) => {
let dedup = dedup_event(&events);
let futures = dedup.into_iter().map(|ev| async move {
let raw = ev.as_json();
let parsed = if ev.kind == Kind::TextNote {
@@ -296,7 +280,6 @@ pub async fn get_local_events(
RichEvent { raw, parsed }
});
let rich_events = join_all(futures).await;
Ok(rich_events)
@@ -305,6 +288,31 @@ pub async fn get_local_events(
}
}
#[tauri::command]
#[specta::specta]
pub async fn listen_local_event(label: &str, state: State<'_, Nostr>) -> Result<(), String> {
let client = &state.client;
let contact_list = state
.contact_list
.lock()
.map_err(|err| err.to_string())?
.clone();
let authors: Vec<PublicKey> = contact_list.into_iter().map(|f| f.public_key).collect();
let sub_id = SubscriptionId::new(label);
let filter = Filter::new()
.kinds(vec![Kind::TextNote, Kind::Repost])
.authors(authors)
.since(Timestamp::now());
// Subscribe
client.subscribe_with_id(sub_id, vec![filter], None).await;
Ok(())
}
#[tauri::command]
#[specta::specta]
pub async fn get_group_events(
@@ -332,7 +340,7 @@ pub async fn get_group_events(
let filter = Filter::new()
.kinds(vec![Kind::TextNote, Kind::Repost])
.limit(20)
.limit(FETCH_LIMIT)
.until(as_of)
.authors(authors);
@@ -376,7 +384,7 @@ pub async fn get_global_events(
let filter = Filter::new()
.kinds(vec![Kind::TextNote, Kind::Repost])
.limit(20)
.limit(FETCH_LIMIT)
.until(as_of);
match client
@@ -417,7 +425,7 @@ pub async fn get_hashtag_events(
};
let filter = Filter::new()
.kinds(vec![Kind::TextNote, Kind::Repost])
.limit(20)
.limit(FETCH_LIMIT)
.until(as_of)
.hashtags(hashtags);
@@ -663,3 +671,15 @@ pub async fn user_to_bech32(user: &str, state: State<'_, Nostr>) -> Result<Strin
},
}
}
#[tauri::command]
#[specta::specta]
pub async fn unlisten(id: &str, state: State<'_, Nostr>) -> Result<(), ()> {
let client = &state.client;
let sub_id = SubscriptionId::new(id);
// Remove subscription
client.unsubscribe(sub_id).await;
Ok(())
}

View File

@@ -10,7 +10,7 @@ use tauri_plugin_notification::NotificationExt;
use crate::nostr::event::RichEvent;
use crate::nostr::utils::parse_event;
use crate::{Nostr, Settings};
use crate::{Nostr, Settings, NEWSFEED_NEG_LIMIT, NOTIFICATION_NEG_LIMIT};
#[derive(Serialize, Type)]
pub struct Account {
@@ -242,8 +242,9 @@ pub async fn load_account(
};
// Get user's contact list
let contacts = client.get_contact_list(None).await.unwrap();
*state.contact_list.lock().unwrap() = contacts;
if let Ok(contacts) = client.get_contact_list(None).await {
*state.contact_list.lock().unwrap() = contacts
};
// Create a subscription for notification
let sub_id = SubscriptionId::new("notification");
@@ -299,8 +300,9 @@ pub async fn load_account(
let window = handle.get_window("main").unwrap();
let state = window.state::<Nostr>();
let client = &state.client;
let contact_list = state.contact_list.lock().unwrap().clone();
let filter = Filter::new()
let notification = Filter::new()
.pubkey(public_key)
.kinds(vec![
Kind::TextNote,
@@ -308,11 +310,39 @@ pub async fn load_account(
Kind::Reaction,
Kind::ZapReceipt,
])
.limit(500);
.limit(NOTIFICATION_NEG_LIMIT);
match client.reconcile(filter, NegentropyOptions::default()).await {
Ok(_) => println!("Sync notification done."),
match client
.reconcile(notification, NegentropyOptions::default())
.await
{
Ok(_) => {
if handle.emit_to(EventTarget::Any, "synced", true).is_err() {
println!("Emit event failed.")
}
}
Err(_) => println!("Sync notification failed."),
};
if !contact_list.is_empty() {
let authors: Vec<PublicKey> = contact_list.into_iter().map(|f| f.public_key).collect();
let newsfeed = Filter::new()
.authors(authors)
.kinds(vec![Kind::TextNote, Kind::Repost])
.limit(NEWSFEED_NEG_LIMIT);
match client
.reconcile(newsfeed, NegentropyOptions::default())
.await
{
Ok(_) => {
if handle.emit_to(EventTarget::Any, "synced", true).is_err() {
println!("Emit event failed.")
}
}
Err(_) => println!("Sync newsfeed failed."),
}
}
});
@@ -324,7 +354,7 @@ pub async fn load_account(
let client = &state.client;
// Handle notifications
if client
client
.handle_notifications(|notification| async {
if let RelayPoolNotification::Message { message, .. } = notification {
if let RelayMessage::Event {
@@ -415,6 +445,24 @@ pub async fn load_account(
{
println!("Emit new notification failed.")
}
} else if id.starts_with("column-") {
let raw = event.as_json();
let parsed = if event.kind == Kind::TextNote {
Some(parse_event(&event.content).await)
} else {
None
};
if app
.emit_to(
EventTarget::window(id),
"new_event",
RichEvent { raw, parsed },
)
.is_err()
{
println!("Emit new notification failed.")
}
} else {
println!("new event: {}", event.as_json())
}
@@ -425,10 +473,6 @@ pub async fn load_account(
Ok(false)
})
.await
.is_ok()
{
print!("Listing for new event...");
}
});
Ok(true)

View File

@@ -187,13 +187,15 @@ pub async fn is_contact_list_empty(state: State<'_, Nostr>) -> Result<bool, ()>
#[tauri::command]
#[specta::specta]
pub async fn check_contact(hex: &str, state: State<'_, Nostr>) -> Result<bool, ()> {
pub async fn check_contact(hex: &str, state: State<'_, Nostr>) -> Result<bool, String> {
let contact_list = state.contact_list.lock().unwrap();
let public_key = PublicKey::from_str(hex).unwrap();
match contact_list.iter().position(|x| x.public_key == public_key) {
Some(_) => Ok(true),
None => Ok(false),
match PublicKey::from_str(hex) {
Ok(public_key) => match contact_list.iter().position(|x| x.public_key == public_key) {
Some(_) => Ok(true),
None => Ok(false),
},
Err(err) => Err(err.to_string()),
}
}