feat: Add support for NIP-51 (#236)

* feat: and follow and interest sets

* feat: improve query

* feat: improve
This commit is contained in:
雨宮蓮
2024-10-07 14:33:20 +07:00
committed by GitHub
parent d841163ba7
commit 090a815f99
37 changed files with 1500 additions and 765 deletions

View File

@@ -3,7 +3,7 @@ use nostr_sdk::prelude::*;
use serde::{Deserialize, Serialize};
use specta::Type;
use std::{str::FromStr, time::Duration};
use tauri::State;
use tauri::{Emitter, Manager, State};
use tauri_specta::Event;
use crate::{common::get_latest_event, NewSettings, Nostr, Settings};
@@ -105,18 +105,14 @@ pub async fn set_contact_list(
#[tauri::command]
#[specta::specta]
pub async fn get_contact_list(state: State<'_, Nostr>) -> Result<Vec<String>, String> {
let client = &state.client;
let contact_list = state.contact_list.lock().await.clone();
println!("Total contacts: {}", contact_list.len());
let vec: Vec<String> = contact_list
.into_iter()
.map(|f| f.public_key.to_hex())
.collect();
match client.get_contact_list(Some(Duration::from_secs(5))).await {
Ok(contact_list) => {
let list = contact_list
.into_iter()
.map(|f| f.public_key.to_hex())
.collect();
Ok(list)
}
Err(err) => Err(err.to_string()),
}
Ok(vec)
}
#[tauri::command]
@@ -200,6 +196,194 @@ pub async fn toggle_contact(
}
}
#[tauri::command]
#[specta::specta]
pub async fn set_group(
title: String,
description: Option<String>,
image: Option<String>,
users: Vec<String>,
state: State<'_, Nostr>,
handle: tauri::AppHandle,
) -> Result<String, String> {
let client = &state.client;
let public_keys: Vec<PublicKey> = users
.iter()
.map(|u| PublicKey::from_str(u).unwrap())
.collect();
let label = title.to_lowercase().replace(" ", "-");
let mut tags: Vec<Tag> = vec![Tag::title(title)];
if let Some(desc) = description {
tags.push(Tag::description(desc))
};
if let Some(img) = image {
let url = UncheckedUrl::new(img);
tags.push(Tag::image(url, None));
}
let builder = EventBuilder::follow_set(label, public_keys.clone()).add_tags(tags);
match client.send_event_builder(builder).await {
Ok(report) => {
tauri::async_runtime::spawn(async move {
let state = handle.state::<Nostr>();
let client = &state.client;
let filter = Filter::new()
.kinds(vec![Kind::TextNote, Kind::Repost])
.authors(public_keys)
.limit(500);
if let Ok(report) = client.reconcile(filter, NegentropyOptions::default()).await {
println!("Received: {}", report.received.len());
handle.emit("synchronized", ()).unwrap();
};
});
Ok(report.id().to_hex())
}
Err(e) => Err(e.to_string()),
}
}
#[tauri::command]
#[specta::specta]
pub async fn get_group(id: String, state: State<'_, Nostr>) -> Result<String, String> {
let client = &state.client;
let event_id = EventId::from_str(&id).map_err(|e| e.to_string())?;
let filter = Filter::new().kind(Kind::FollowSet).id(event_id);
match client
.get_events_of(
vec![filter],
EventSource::both(Some(Duration::from_secs(5))),
)
.await
{
Ok(events) => match get_latest_event(&events) {
Some(ev) => Ok(ev.as_json()),
None => Err("Not found.".to_string()),
},
Err(e) => Err(e.to_string()),
}
}
#[tauri::command]
#[specta::specta]
pub async fn get_all_groups(state: State<'_, Nostr>) -> Result<Vec<String>, String> {
let client = &state.client;
let signer = client.signer().await.map_err(|e| e.to_string())?;
let public_key = signer.public_key().await.map_err(|e| e.to_string())?;
let filter = Filter::new().kind(Kind::FollowSet).author(public_key);
match client
.get_events_of(vec![filter], EventSource::Database)
.await
{
Ok(events) => {
let data: Vec<String> = events.iter().map(|ev| ev.as_json()).collect();
Ok(data)
}
Err(e) => Err(e.to_string()),
}
}
#[tauri::command]
#[specta::specta]
pub async fn set_interest(
title: String,
description: Option<String>,
image: Option<String>,
hashtags: Vec<String>,
state: State<'_, Nostr>,
handle: tauri::AppHandle,
) -> Result<String, String> {
let client = &state.client;
let label = title.to_lowercase().replace(" ", "-");
let mut tags: Vec<Tag> = vec![Tag::title(title)];
if let Some(desc) = description {
tags.push(Tag::description(desc))
};
if let Some(img) = image {
let url = UncheckedUrl::new(img);
tags.push(Tag::image(url, None));
}
let builder = EventBuilder::interest_set(label, hashtags.clone()).add_tags(tags);
match client.send_event_builder(builder).await {
Ok(report) => {
tauri::async_runtime::spawn(async move {
let state = handle.state::<Nostr>();
let client = &state.client;
let filter = Filter::new()
.kinds(vec![Kind::TextNote, Kind::Repost])
.hashtags(hashtags)
.limit(500);
if let Ok(report) = client.reconcile(filter, NegentropyOptions::default()).await {
println!("Received: {}", report.received.len());
handle.emit("synchronized", ()).unwrap();
};
});
Ok(report.id().to_hex())
}
Err(e) => Err(e.to_string()),
}
}
#[tauri::command]
#[specta::specta]
pub async fn get_interest(id: String, state: State<'_, Nostr>) -> Result<String, String> {
let client = &state.client;
let event_id = EventId::from_str(&id).map_err(|e| e.to_string())?;
let filter = Filter::new()
.kinds(vec![Kind::Interests, Kind::InterestSet])
.id(event_id);
match client
.get_events_of(
vec![filter],
EventSource::both(Some(Duration::from_secs(5))),
)
.await
{
Ok(events) => match get_latest_event(&events) {
Some(ev) => Ok(ev.as_json()),
None => Err("Not found.".to_string()),
},
Err(e) => Err(e.to_string()),
}
}
#[tauri::command]
#[specta::specta]
pub async fn get_all_interests(state: State<'_, Nostr>) -> Result<Vec<String>, String> {
let client = &state.client;
let signer = client.signer().await.map_err(|e| e.to_string())?;
let public_key = signer.public_key().await.map_err(|e| e.to_string())?;
let filter = Filter::new()
.kinds(vec![Kind::InterestSet, Kind::Interests])
.author(public_key);
match client
.get_events_of(vec![filter], EventSource::Database)
.await
{
Ok(events) => {
let data: Vec<String> = events.iter().map(|ev| ev.as_json()).collect();
Ok(data)
}
Err(e) => Err(e.to_string()),
}
}
#[tauri::command]
#[specta::specta]
pub async fn get_mention_list(state: State<'_, Nostr>) -> Result<Vec<Mention>, String> {
@@ -230,61 +414,6 @@ pub async fn get_mention_list(state: State<'_, Nostr>) -> Result<Vec<Mention>, S
Ok(data)
}
#[tauri::command]
#[specta::specta]
pub async fn set_lume_store(
key: String,
content: String,
state: State<'_, Nostr>,
) -> Result<String, String> {
let client = &state.client;
let signer = client.signer().await.map_err(|e| e.to_string())?;
let public_key = signer.public_key().await.map_err(|e| e.to_string())?;
let encrypted = signer
.nip44_encrypt(&public_key, content)
.await
.map_err(|e| e.to_string())?;
let tag = Tag::identifier(key);
let builder = EventBuilder::new(Kind::ApplicationSpecificData, encrypted, vec![tag]);
match client.send_event_builder(builder).await {
Ok(event_id) => Ok(event_id.to_string()),
Err(err) => Err(err.to_string()),
}
}
#[tauri::command]
#[specta::specta]
pub async fn get_lume_store(key: String, state: State<'_, Nostr>) -> Result<String, String> {
let client = &state.client;
let signer = client.signer().await.map_err(|e| e.to_string())?;
let public_key = signer.public_key().await.map_err(|e| e.to_string())?;
let filter = Filter::new()
.author(public_key)
.kind(Kind::ApplicationSpecificData)
.identifier(key)
.limit(10);
match client
.get_events_of(vec![filter], EventSource::Database)
.await
{
Ok(events) => {
if let Some(event) = get_latest_event(&events) {
match signer.nip44_decrypt(&public_key, &event.content).await {
Ok(decrypted) => Ok(decrypted),
Err(_) => Err(event.content.to_string()),
}
} else {
Err("Not found".into())
}
}
Err(err) => Err(err.to_string()),
}
}
#[tauri::command]
#[specta::specta]
pub async fn set_wallet(uri: &str, state: State<'_, Nostr>) -> Result<bool, String> {