Skip to content

Commit

Permalink
nostrs: Add Client
Browse files Browse the repository at this point in the history
  • Loading branch information
bouzuya committed Mar 4, 2023
1 parent 45f27b3 commit 00cd0d7
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 116 deletions.
139 changes: 107 additions & 32 deletions nostrs/src/client.rs
@@ -1,17 +1,119 @@
use std::{collections::HashMap, time::Duration};

use anyhow::{bail, Context};
use nostr_sdk::{
prelude::{Event, FromSkStr, Keys, SubscriptionFilter},
Client, Options, RelayOptions,
prelude::{
Contact, Event, EventId, FromSkStr, Keys, Kind, SubscriptionFilter, Tag, XOnlyPublicKey,
},
Options, RelayOptions,
};

use crate::{config, keypair};

pub struct Client(nostr_sdk::Client);

impl Client {
pub async fn delete_event(&self, event_id: EventId) -> anyhow::Result<EventId> {
Ok(self.0.delete_event::<String>(event_id, None).await?)
}

pub async fn dislike(&self, event_id: EventId) -> anyhow::Result<EventId> {
let public_key = self.get_text_note_public_key_by_event_id(event_id).await?;
Ok(self.0.dislike(event_id, public_key).await?)
}

pub async fn get_contact_list(&self) -> anyhow::Result<Vec<Contact>> {
let filter = SubscriptionFilter::new()
.authors(vec![self.0.keys().public_key()])
.kind(Kind::ContactList)
.limit(1);
let timeout = Duration::from_secs(10);
let event = self
.get_event_of(vec![filter], Some(timeout))
.await?
.context("contact_list not found")?;
let mut map = HashMap::new();
for tag in event.tags {
let contact = match tag {
Tag::ContactList {
pk,
relay_url,
alias,
} => Contact::new(pk, relay_url, alias),
Tag::PubKey(pk, _) => Contact::new::<String>(pk, None, None),
_ => bail!("invalid tag: {tag:?}"),
};
map.insert(contact.pk, contact);
}
Ok(map.into_values().collect::<Vec<Contact>>())
}

/// Returns only one event with the latest created_at.
pub async fn get_event_of(
&self,
filters: Vec<SubscriptionFilter>,
timeout: Option<Duration>,
) -> anyhow::Result<Option<Event>> {
// the events is in ascending order by created_at.
let events = self.get_events_of(filters, timeout).await?;
Ok(events.last().cloned())
}

/// Returns events in ascending order by created_at, with duplicate id's removed.
pub async fn get_events_of(
&self,
filters: Vec<SubscriptionFilter>,
timeout: Option<Duration>,
) -> anyhow::Result<Vec<Event>> {
let events = self.0.get_events_of(filters, timeout).await?;
let mut map = HashMap::new();
for event in events {
map.insert(event.id, event);
}
let mut events = map.into_values().collect::<Vec<Event>>();
events.sort_by_key(|event| event.created_at);
Ok(events)
}

pub fn keys(&self) -> Keys {
self.0.keys()
}

pub async fn like(&self, event_id: EventId) -> anyhow::Result<EventId> {
let public_key = self.get_text_note_public_key_by_event_id(event_id).await?;
Ok(self.0.like(event_id, public_key).await?)
}

pub async fn publish_text_note(
&self,
content: String,
tags: &[Tag],
) -> anyhow::Result<EventId> {
Ok(self.0.publish_text_note(content, tags).await?)
}

async fn get_text_note_public_key_by_event_id(
&self,
event_id: EventId,
) -> anyhow::Result<XOnlyPublicKey> {
let filter = SubscriptionFilter::new()
.kind(Kind::TextNote)
.id(event_id.to_hex())
.limit(1);
let timeout = Duration::from_secs(10);
let event = self
.get_event_of(vec![filter], Some(timeout))
.await?
.with_context(|| format!("event ({event_id:?}) not found"))?;
Ok(event.pubkey)
}
}

pub async fn new_client() -> anyhow::Result<Client> {
let private_key = keypair::load()?;
let my_keys = Keys::from_sk_str(private_key.as_str())?;

let client = Client::new_with_opts(&my_keys, Options::default().wait_for_send(true));
let client = nostr_sdk::Client::new_with_opts(&my_keys, Options::default().wait_for_send(true));
let config = config::load()?;
for (url, options) in config.relays.iter() {
client
Expand All @@ -24,34 +126,7 @@ pub async fn new_client() -> anyhow::Result<Client> {
}
client.connect().await;

Ok(client)
}

/// Returns only one event with the latest created_at.
pub async fn get_event_of(
client: &Client,
filters: Vec<SubscriptionFilter>,
timeout: Option<Duration>,
) -> anyhow::Result<Option<Event>> {
// the events is in ascending order by created_at.
let events = get_events_of(client, filters, timeout).await?;
Ok(events.last().cloned())
}

/// Returns events in ascending order by created_at, with duplicate id's removed.
pub async fn get_events_of(
client: &Client,
filters: Vec<SubscriptionFilter>,
timeout: Option<Duration>,
) -> anyhow::Result<Vec<Event>> {
let events = client.get_events_of(filters, timeout).await?;
let mut map = HashMap::new();
for event in events {
map.insert(event.id, event);
}
let mut events = map.into_values().collect::<Vec<Event>>();
events.sort_by_key(|event| event.created_at);
Ok(events)
Ok(Client(client))
}

#[cfg(test)]
Expand All @@ -68,7 +143,7 @@ mod tests {
.authors(vec![client.keys().public_key()])
.kind(Kind::ContactList);
let timeout = Duration::from_secs(10);
let events = get_events_of(&client, vec![filter], Some(timeout)).await?;
let events = client.get_events_of(vec![filter], Some(timeout)).await?;
println!("{}", serde_json::to_string_pretty(&events)?);
Ok(())
}
Expand Down
42 changes: 4 additions & 38 deletions nostrs/src/handler/contact/list.rs
@@ -1,7 +1,6 @@
use std::{collections::HashMap, time::Duration};

use anyhow::bail;
use nostr_sdk::prelude::{Contact, Kind, Metadata, SubscriptionFilter, Tag, Timestamp};
use nostr_sdk::prelude::{Kind, Metadata, SubscriptionFilter, Timestamp};

use crate::{
client::new_client,
Expand All @@ -17,47 +16,14 @@ pub async fn handle() -> anyhow::Result<()> {
Some(t) if t >= now - Duration::from_secs(60 * 60) => contact_cache.contacts,
Some(_) | None => {
let mut map = HashMap::new();
let filter = SubscriptionFilter::new()
.authors(vec![client.keys().public_key()])
.kind(Kind::ContactList)
.limit(1);
let timeout = Duration::from_secs(10);
let events = client.get_events_of(vec![filter], Some(timeout)).await?;

let mut contact_list = HashMap::new();
let mut contact_list_timestamp = None;
for event in events {
let mut list = HashMap::new();
for tag in event.tags {
let contact = match tag {
Tag::ContactList {
pk,
relay_url,
alias,
} => Contact::new(pk, relay_url, alias),
Tag::PubKey(pk, _) => Contact::new::<String>(pk, None, None),
_ => bail!("invalid tag: {tag:?}"),
};
list.insert(contact.pk, contact);
}
if let Some(timestamp) = contact_list_timestamp {
if timestamp < event.created_at {
contact_list = list;
contact_list_timestamp = Some(event.created_at);
}
} else {
contact_list = list;
contact_list_timestamp = Some(event.created_at);
}
}
for contact in contact_list.values() {
let contact_list = client.get_contact_list().await?;
for contact in contact_list {
let filter = SubscriptionFilter::new()
.authors(vec![contact.pk])
.kind(Kind::Metadata)
.limit(1);
let timeout = Duration::from_secs(10);
let events = client.get_events_of(vec![filter], Some(timeout)).await?;
if let Some(event) = events.first() {
if let Some(event) = client.get_event_of(vec![filter], Some(timeout)).await? {
let metadata: Metadata = serde_json::from_str(event.content.as_str())?;
map.insert(contact.pk, Some(metadata));
}
Expand Down
6 changes: 4 additions & 2 deletions nostrs/src/handler/metadata/get.rs
Expand Up @@ -13,8 +13,10 @@ pub async fn handle() -> anyhow::Result<()> {
.author(client.keys().public_key())
.limit(1);
let timeout = Duration::from_secs(10);
let events = client.get_events_of(vec![filter], Some(timeout)).await?;
let event = events.first().context("metadata not found")?;
let event = client
.get_event_of(vec![filter], Some(timeout))
.await?
.context("metadata not found")?;
let metadata: Metadata = serde_json::from_str(event.content.as_str())?;
println!("{}", serde_json::to_string_pretty(&metadata)?);
Ok(())
Expand Down
2 changes: 1 addition & 1 deletion nostrs/src/handler/text_note/delete.rs
Expand Up @@ -4,6 +4,6 @@ use crate::{client::new_client, event_id::event_id_from_hex_or_bech32};
pub async fn handle(event_id: String) -> anyhow::Result<()> {
let event_id = event_id_from_hex_or_bech32(event_id.as_str())?;
let client = new_client().await?;
client.delete_event::<String>(event_id, None).await?;
client.delete_event(event_id).await?;
Ok(())
}
17 changes: 1 addition & 16 deletions nostrs/src/handler/text_note/dislike.rs
@@ -1,24 +1,9 @@
use std::time::Duration;

use anyhow::bail;
use nostr_sdk::prelude::{Kind, SubscriptionFilter};

use crate::{client::new_client, event_id::event_id_from_hex_or_bech32};

// NIP-25 <https://github.com/nostr-protocol/nips/blob/master/25.md>
pub async fn handle(event_id: String) -> anyhow::Result<()> {
let event_id = event_id_from_hex_or_bech32(event_id.as_str())?;
let filter = SubscriptionFilter::new()
.kind(Kind::TextNote)
.id(event_id.to_hex())
.limit(1);
let timeout = Duration::from_secs(10);
let client = new_client().await?;
let events = client.get_events_of(vec![filter], Some(timeout)).await?;
if events.is_empty() {
bail!("event ({event_id:?}) not found");
}
let public_key = events[0].pubkey;
client.dislike(event_id, public_key).await?;
client.dislike(event_id).await?;
Ok(())
}
17 changes: 1 addition & 16 deletions nostrs/src/handler/text_note/like.rs
@@ -1,24 +1,9 @@
use std::time::Duration;

use anyhow::bail;
use nostr_sdk::prelude::{Kind, SubscriptionFilter};

use crate::{client::new_client, event_id::event_id_from_hex_or_bech32};

// NIP-25 <https://github.com/nostr-protocol/nips/blob/master/25.md>
pub async fn handle(event_id: String) -> anyhow::Result<()> {
let event_id = event_id_from_hex_or_bech32(event_id.as_str())?;
let filter = SubscriptionFilter::new()
.kind(Kind::TextNote)
.id(event_id.to_hex())
.limit(1);
let timeout = Duration::from_secs(10);
let client = new_client().await?;
let events = client.get_events_of(vec![filter], Some(timeout)).await?;
if events.is_empty() {
bail!("event ({event_id:?}) not found");
}
let public_key = events[0].pubkey;
client.like(event_id, public_key).await?;
client.like(event_id).await?;
Ok(())
}
13 changes: 2 additions & 11 deletions nostrs/src/handler/text_note/list.rs
@@ -1,4 +1,4 @@
use std::{cmp::Reverse, collections::HashSet, time::Duration};
use std::time::Duration;

use nostr_sdk::prelude::{Kind, SubscriptionFilter, ToBech32};
use time::format_description::well_known::Rfc3339;
Expand All @@ -13,16 +13,7 @@ pub async fn handle() -> anyhow::Result<()> {
.limit(32);
let timeout = Duration::from_secs(10);
let events = client.get_events_of(vec![filter], Some(timeout)).await?;
let mut unique_events = vec![];
let mut used = HashSet::new();
for event in events {
if used.insert(event.id) {
unique_events.push(event);
}
}
unique_events.sort_by_key(|event| Reverse(event.created_at));

for event in unique_events {
for event in events.into_iter().rev() {
println!("{}", event.id.to_bech32()?);
println!("{} : ", event.pubkey);
println!(
Expand Down

0 comments on commit 00cd0d7

Please sign in to comment.