Skip to content

Commit

Permalink
nostr: add EventBuilder::comment
Browse files Browse the repository at this point in the history
* Add support to uppercase `TagStandard::Kind`
* Add `comment` example to `nostr-sdk` crate

Closes #611
Closes #612

Co-authored-by: Yuki Kishimoto <[email protected]>
Signed-off-by: Yuki Kishimoto <[email protected]>
  • Loading branch information
reyamir and yukibtc committed Nov 10, 2024
1 parent 78e77c4 commit 8c6b2a1
Show file tree
Hide file tree
Showing 10 changed files with 245 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

* nostr: add `SingleLetterTag::as_str` and `TagKind::as_str` ([Yuki Kishimoto])
* nostr: add `Kind::Comment` ([reyamir])
* nostr: add `EventBuilder::comment` ([reyamir])

### Fixed

Expand Down
22 changes: 22 additions & 0 deletions bindings/nostr-sdk-ffi/src/protocol/event/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,28 @@ impl EventBuilder {
}
}

/// Comment
///
/// If no `root` is passed, the `rely_to` will be used for root `e` tag.
///
/// <https://github.com/nostr-protocol/nips/blob/master/22.md>
#[uniffi::constructor(default(root = None, relay_url = None))]
pub fn comment(
content: String,
comment_to: &Event,
root: Option<Arc<Event>>,
relay_url: Option<String>,
) -> Self {
Self {
inner: nostr::EventBuilder::comment(
content,
comment_to.deref(),
root.as_ref().map(|e| e.as_ref().deref()),
relay_url.map(UncheckedUrl::from),
),
}
}

/// Long-form text note (generally referred to as "articles" or "blog posts").
///
/// <https://github.com/nostr-protocol/nips/blob/master/23.md>
Expand Down
18 changes: 16 additions & 2 deletions bindings/nostr-sdk-ffi/src/protocol/event/tag/standard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ pub enum TagStandard {
marker: Option<Marker>,
/// Should be the public key of the author of the referenced event
public_key: Option<Arc<PublicKey>>,
/// Whether the e tag is an uppercase E or not
uppercase: bool,
},
/// Git clone (`clone` tag)
///
Expand Down Expand Up @@ -108,6 +110,8 @@ pub enum TagStandard {
},
Kind {
kind: KindEnum,
/// Whether the k tag is an uppercase K or not
uppercase: bool,
},
RelayUrl {
relay_url: String,
Expand Down Expand Up @@ -279,11 +283,13 @@ impl From<tag::TagStandard> for TagStandard {
relay_url,
marker,
public_key,
uppercase,
} => Self::EventTag {
event_id: Arc::new(event_id.into()),
relay_url: relay_url.map(|u| u.to_string()),
marker: marker.map(|m| m.into()),
public_key: public_key.map(|p| Arc::new(p.into())),
uppercase,
},
tag::TagStandard::GitClone(urls) => Self::GitClone {
urls: urls.into_iter().map(|r| r.to_string()).collect(),
Expand Down Expand Up @@ -351,7 +357,10 @@ impl From<tag::TagStandard> for TagStandard {
tag::TagStandard::ExternalIdentity(identity) => Self::ExternalIdentity {
identity: identity.into(),
},
tag::TagStandard::Kind(kind) => Self::Kind { kind: kind.into() },
tag::TagStandard::Kind { kind, uppercase } => Self::Kind {
kind: kind.into(),
uppercase,
},
tag::TagStandard::Relay(url) => Self::RelayUrl {
relay_url: url.to_string(),
},
Expand Down Expand Up @@ -475,11 +484,13 @@ impl TryFrom<TagStandard> for tag::TagStandard {
relay_url,
marker,
public_key,
uppercase,
} => Ok(Self::Event {
event_id: **event_id,
relay_url: relay_url.map(UncheckedUrl::from),
marker: marker.map(nip10::Marker::from),
public_key: public_key.map(|p| **p),
uppercase,
}),
TagStandard::GitClone { urls } => {
let mut parsed_urls: Vec<Url> = Vec::with_capacity(urls.len());
Expand Down Expand Up @@ -544,7 +555,10 @@ impl TryFrom<TagStandard> for tag::TagStandard {
coordinate: coordinate.as_ref().deref().clone(),
relay_url: relay_url.map(UncheckedUrl::from),
}),
TagStandard::Kind { kind } => Ok(Self::Kind(kind.into())),
TagStandard::Kind { kind, uppercase } => Ok(Self::Kind {
kind: kind.into(),
uppercase,
}),
TagStandard::RelayUrl { relay_url } => Ok(Self::Relay(UncheckedUrl::from(relay_url))),
TagStandard::POW { nonce, difficulty } => Ok(Self::POW {
nonce: nonce.parse()?,
Expand Down
22 changes: 22 additions & 0 deletions bindings/nostr-sdk-js/src/protocol/event/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,28 @@ impl JsEventBuilder {
}
}

/// Comment
///
/// If no `root` is passed, the `rely_to` will be used for root `e` tag.
///
/// <https://github.com/nostr-protocol/nips/blob/master/22.md>
#[wasm_bindgen(js_name = comment)]
pub fn comment(
content: &str,
comment_to: &JsEvent,
root: Option<JsEvent>,
relay_url: Option<String>,
) -> Self {
Self {
inner: EventBuilder::comment(
content,
comment_to.deref(),
root.as_deref(),
relay_url.map(UncheckedUrl::from),
),
}
}

/// Long-form text note (generally referred to as "articles" or "blog posts").
///
/// <https://github.com/nostr-protocol/nips/blob/master/23.md>
Expand Down
7 changes: 5 additions & 2 deletions crates/nostr-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,16 @@ name = "blacklist"
required-features = ["all-nips"]

[[example]]
name = "client-with-opts"
name = "client"
required-features = ["all-nips"]

[[example]]
name = "client"
name = "client-with-opts"
required-features = ["all-nips"]

[[example]]
name = "comment"

[[example]]
name = "fetch-events"
required-features = ["all-nips"]
Expand Down
32 changes: 32 additions & 0 deletions crates/nostr-sdk/examples/comment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) 2022-2023 Yuki Kishimoto
// Copyright (c) 2023-2024 Rust Nostr Developers
// Distributed under the MIT software license

use nostr_sdk::prelude::*;

#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt::init();

let keys = Keys::generate();
let client = Client::builder().signer(keys).build();

client.add_relay("wss://relay.damus.io/").await?;
client.add_relay("wss://relay.primal.net/").await?;

client.connect().await;

let event_id =
EventId::from_bech32("note1hrrgx2309my3wgeecx2tt6fl2nl8hcwl0myr3xvkcqpnq24pxg2q06armr")?;
let events = client
.fetch_events(vec![Filter::new().id(event_id)], None)
.await?;

let comment_to = events.first().unwrap();
let builder = EventBuilder::comment("This is a reply", comment_to, None, None);

let output = client.send_event_builder(builder).await?;
println!("Output: {:?}", output);

Ok(())
}
1 change: 1 addition & 0 deletions crates/nostr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ The following crate feature flags are available:
|| [18 - Reposts](https://github.com/nostr-protocol/nips/blob/master/18.md) |
|| [19 - bech32-encoded entities](https://github.com/nostr-protocol/nips/blob/master/19.md) |
|| [21 - URI scheme](https://github.com/nostr-protocol/nips/blob/master/21.md) |
|| [22 - Comment](https://github.com/nostr-protocol/nips/blob/master/22.md) |
|| [23 - Long-form Content](https://github.com/nostr-protocol/nips/blob/master/23.md) |
|| [24 - Extra metadata fields and tags](https://github.com/nostr-protocol/nips/blob/master/24.md) |
|| [25 - Reactions](https://github.com/nostr-protocol/nips/blob/master/25.md) |
Expand Down
117 changes: 115 additions & 2 deletions crates/nostr/src/event/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,7 @@ impl EventBuilder {
relay_url: relay_url.clone(),
marker: Some(Marker::Root),
public_key: Some(root.pubkey),
uppercase: false,
}));
tags.push(Tag::public_key(root.pubkey));

Expand All @@ -464,6 +465,7 @@ impl EventBuilder {
relay_url: relay_url.clone(),
marker: Some(Marker::Root),
public_key: Some(reply_to.pubkey),
uppercase: false,
}));
}
}
Expand All @@ -474,6 +476,7 @@ impl EventBuilder {
relay_url,
marker: Some(Marker::Reply),
public_key: Some(reply_to.pubkey),
uppercase: false,
}));
tags.push(Tag::public_key(reply_to.pubkey));

Expand All @@ -496,6 +499,104 @@ impl EventBuilder {
Self::new(Kind::TextNote, content, tags)
}

/// Comment
///
/// If no `root` is passed, the `comment_to` will be used for root `e` tag.
///
/// <https://github.com/nostr-protocol/nips/blob/master/22.md>
pub fn comment<S>(
content: S,
comment_to: &Event,
root: Option<&Event>,
relay_url: Option<UncheckedUrl>,
) -> Self
where
S: Into<String>,
{
// The added tags will be at least 4
let mut tags: Vec<Tag> = Vec::with_capacity(4);

// Add `E` and `K` tag of **root** event
if let Some(root) = root {
// ID and author
tags.push(Tag::from_standardized_without_cell(TagStandard::Event {
event_id: root.id,
relay_url: relay_url.clone(),
marker: None,
public_key: Some(root.pubkey),
uppercase: true,
}));

// Kind
tags.push(Tag::from_standardized_without_cell(TagStandard::Kind {
kind: root.kind,
uppercase: true,
}));

// Add others `p` tags
tags.extend(
root.tags
.iter()
.filter(|t| {
t.kind()
== TagKind::SingleLetter(SingleLetterTag {
character: Alphabet::P,
uppercase: false,
})
})
.cloned(),
);
} else {
// ID and author
tags.push(Tag::from_standardized_without_cell(TagStandard::Event {
event_id: comment_to.id,
relay_url: relay_url.clone(),
marker: None,
public_key: Some(comment_to.pubkey),
uppercase: true,
}));

// Kind
tags.push(Tag::from_standardized_without_cell(TagStandard::Kind {
kind: comment_to.kind,
uppercase: true,
}));
}

// Add `e` tag of event author
tags.push(Tag::from_standardized_without_cell(TagStandard::Event {
event_id: comment_to.id,
relay_url,
marker: None,
public_key: Some(comment_to.pubkey),
uppercase: false,
}));

// Add `k` tag of event kind
tags.push(Tag::from_standardized_without_cell(TagStandard::Kind {
kind: comment_to.kind,
uppercase: false,
}));

// Add others `p` tags of comment_to event
tags.extend(
comment_to
.tags
.iter()
.filter(|t| {
t.kind()
== TagKind::SingleLetter(SingleLetterTag {
character: Alphabet::P,
uppercase: false,
})
})
.cloned(),
);

// Compose event
Self::new(Kind::Comment, content, tags)
}

/// Long-form text note (generally referred to as "articles" or "blog posts").
///
/// <https://github.com/nostr-protocol/nips/blob/master/23.md>
Expand Down Expand Up @@ -561,6 +662,7 @@ impl EventBuilder {
relay_url,
marker: None,
public_key: None,
uppercase: false,
})],
))
}
Expand All @@ -580,6 +682,7 @@ impl EventBuilder {
marker: None,
// NOTE: not add public key since it's already included as `p` tag
public_key: None,
uppercase: false,
}),
Tag::public_key(event.pubkey),
],
Expand All @@ -595,9 +698,13 @@ impl EventBuilder {
marker: None,
// NOTE: not add public key since it's already included as `p` tag
public_key: None,
uppercase: false,
}),
Tag::public_key(event.pubkey),
Tag::from_standardized_without_cell(TagStandard::Kind(event.kind)),
Tag::from_standardized_without_cell(TagStandard::Kind {
kind: event.kind,
uppercase: false,
}),
],
)
}
Expand Down Expand Up @@ -660,7 +767,10 @@ impl EventBuilder {
tags.push(Tag::public_key(public_key));

if let Some(kind) = kind {
tags.push(Tag::from_standardized_without_cell(TagStandard::Kind(kind)));
tags.push(Tag::from_standardized_without_cell(TagStandard::Kind {
kind,
uppercase: false,
}));
}

Self::new(Kind::Reaction, reaction, tags)
Expand Down Expand Up @@ -691,6 +801,7 @@ impl EventBuilder {
relay_url: relay_url.map(|u| u.into()),
marker: None,
public_key: None,
uppercase: false,
})],
)
}
Expand All @@ -711,6 +822,7 @@ impl EventBuilder {
relay_url: Some(relay_url.into()),
marker: Some(Marker::Root),
public_key: None,
uppercase: false,
})],
)
}
Expand Down Expand Up @@ -1133,6 +1245,7 @@ impl EventBuilder {
relay_url: relay_url.clone(),
marker: None,
public_key: None,
uppercase: false,
});
tags.extend_from_slice(&[a_tag.clone(), badge_award_event_tag]);
}
Expand Down
Loading

0 comments on commit 8c6b2a1

Please sign in to comment.